diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f0957c311 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +packages/grpc-js-xds/src/generated/** linguist-generated +packages/grpc-js-xds/interop/generated/** linguist-generated diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..a5200c9cd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,21 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +### Problem description +A clear and concise description of what the problem is. + +### Reproduction steps +Give very precise steps you've discovered to reproduce your problem. If possible and applicable, provide us with a repository we can clone that contains a reproduction case. Also if possible and applicable, please include a Dockerfile that exhibits the problem if it's specific to a certain environment. Bug reports with no reproduction steps will be closed. + +### Environment + - OS name, version and architecture: [e.g. Linux Ubuntu 18.04 amd64] + - Node version [e.g. 8.10.0] + - Node installation method [e.g. nvm] + - If applicable, compiler version [e.g. clang 3.8.0-2ubuntu4] + - Package name and version [e.g. gRPC@1.12.0] + +### Additional context +Add any other context about the problem here. If possible, attach full logs. Do not try to omit anything for brevity, but instead include absolutely everything. Please try and set your operating system's locale to English, so that logs contain error messages in the English language. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..13a22d907 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +### Additional context +Add any other context about the feature request here. diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 000000000..119e4840b --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,2 @@ +daysUntilLock: 90 +lockComment: false diff --git a/.github/workflows/grpc-tools-build.yml b/.github/workflows/grpc-tools-build.yml new file mode 100644 index 000000000..f32a688c4 --- /dev/null +++ b/.github/workflows/grpc-tools-build.yml @@ -0,0 +1,72 @@ +name: grpc-tools Build + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + linux_build: + name: Linux grpc-tools Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build + run: | + docker build -t kokoro-native-image tools/release/native + docker run -v /var/run/docker.sock:/var/run/docker.sock -v $GITHUB_WORKSPACE:$GITHUB_WORKSPACE kokoro-native-image $GITHUB_WORKSPACE/packages/grpc-tools/build_binaries.sh + - uses: actions/upload-artifact@v2 + with: + name: grpc-tools_linux + path: artifacts/ + macos_build: + name: Macos grpc-tools Build + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build + run: packages/grpc-tools/build_binaries.sh + - uses: actions/upload-artifact@v2 + with: + name: grpc-tools_macos + path: artifacts/ + windows_build: + name: Windows grpc-tools Build + runs-on: windows-latest + strategy: + matrix: + arch: [ia32, x64] + env: + ARCH: ${{matrix.arch}} + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build + run: powershell -File ./packages/grpc-tools/build_binaries.ps1 + shell: cmd + - uses: actions/upload-artifact@v1 + with: + name: grpc-tools_windows_${{matrix.arch}} + path: artifacts/ + combine_artifacts: + name: Combine grpc-tools artifacts + runs-on: ubuntu-latest + needs: [linux_build, macos_build, windows_build] + steps: + - uses: actions/download-artifact@v2 + - name: Copy + run: | + mkdir artifacts + cp -r ./**/* artifacts/ + - uses: actions/upload-artifact@v2 + with: + name: combined-artifacts + path: artifacts/ diff --git a/.gitignore b/.gitignore index 583683394..fce837e45 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,18 @@ npm-debug.log yarn-error.log yarn.lock +# Emacs temp files *~ +\#*\# +.\#* -packages/grpc-native-core/src/node/ \ No newline at end of file +.nyc_output/ +reports/ + +package-lock.json + +# Test generated files +coverage + +# Node's bash completion file +.node_bash_completion diff --git a/.gitmodules b/.gitmodules index 5d82e5297..f54fa6afc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,21 @@ -[submodule "packages/grpc-native-core/deps/grpc"] - path = packages/grpc-native-core/deps/grpc - url = https://github.com/grpc/grpc.git +[submodule "packages/grpc-tools/deps/protobuf"] + path = packages/grpc-tools/deps/protobuf + url = https://github.com/protocolbuffers/protobuf +[submodule "packages/proto-loader/deps/gapic-showcase"] + path = packages/proto-loader/deps/gapic-showcase + url = https://github.com/googleapis/gapic-showcase.git +[submodule "packages/proto-loader/deps/googleapis"] + path = packages/proto-loader/deps/googleapis + url = https://github.com/googleapis/googleapis.git +[submodule "packages/grpc-js-xds/deps/envoy-api"] + path = packages/grpc-js-xds/deps/envoy-api + url = https://github.com/envoyproxy/data-plane-api.git +[submodule "packages/grpc-js-xds/deps/googleapis"] + path = packages/grpc-js-xds/deps/googleapis + url = https://github.com/googleapis/googleapis.git +[submodule "packages/grpc-js-xds/deps/protoc-gen-validate"] + path = packages/grpc-js-xds/deps/protoc-gen-validate + url = https://github.com/envoyproxy/protoc-gen-validate.git +[submodule "packages/grpc-js-xds/deps/xds"] + path = packages/grpc-js-xds/deps/xds + url = https://github.com/cncf/xds.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 94bb48a55..000000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: node_js -node_js: - - "4" - - "5" - - "6" - - "7" - - "8" - - "node" -env: - - CXX=g++-4.8 -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 -install: - - npm install - - gulp setup -script: - - gulp native.test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..aa9b54472 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# How to contribute + +We definitely welcome patches and contributions to grpc-node! Please read the gRPC +organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md) +and [contribution guidelines](https://github.com/grpc/grpc-community/blob/master/CONTRIBUTING.md) before proceeding. diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 000000000..d6ff26747 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1 @@ +This repository is governed by the gRPC organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md). diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 000000000..bdcef2e26 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,24 @@ +This page lists all active maintainers of this repository. If you were a +maintainer and would like to add your name to the Emeritus list, please send us a +PR. + +See [GOVERNANCE.md](https://github.com/grpc/grpc-community/blob/master/governance.md) +for governance guidelines and how to become a maintainer. +See [CONTRIBUTING.md](https://github.com/grpc/grpc-community/blob/master/CONTRIBUTING.md) +for general contribution guidelines. + +## Maintainers (in alphabetical order) + + - [jtattermusch](https://github.com/jtattermusch), Google Inc. + - [murgatroid99](https://github.com/murgatroid99), Google Inc. + - [nicolasnoble](https://github.com/nicolasnoble), Google Inc. + - [srini100](https://github.com/srini100), Google Inc. + - [wenbozhu](https://github.com/wenbozhu), Google Inc. + + ## Emeritus Maintainers (in alphabetical order) + - [jiangtaoli2016](https://github.com/jiangtaoli2016), Google Inc. + - [kjin](https://github.com/kjin), Google Inc. + - [matt-kwong](https://github.com/matt-kwong), Google Inc. + - [ofrobots](https://github.com/ofrobots), Google Inc. + - [WeiranFang](https://github.com/WeiranFang), Google Inc. + diff --git a/PACKAGE-COMPARISON.md b/PACKAGE-COMPARISON.md new file mode 100644 index 000000000..e6cee8934 --- /dev/null +++ b/PACKAGE-COMPARISON.md @@ -0,0 +1,32 @@ +# Feature comparison of `grpc` and `@grpc/grpc-js` packages + +Feature | `grpc` (deprecated) | `@grpc/grpc-js` +--------|--------|---------- +Client | :heavy_check_mark: | :heavy_check_mark: +Server | :heavy_check_mark: | :heavy_check_mark: +Unary RPCs | :heavy_check_mark: | :heavy_check_mark: +Streaming RPCs | :heavy_check_mark: | :heavy_check_mark: +Deadlines | :heavy_check_mark: | :heavy_check_mark: +Cancellation | :heavy_check_mark: | :heavy_check_mark: +Automatic Reconnection | :heavy_check_mark: | :heavy_check_mark: +Per-message Compression | :heavy_check_mark: | :heavy_check_mark: (except messages sent by the server) +Channel State | :heavy_check_mark: | :heavy_check_mark: +JWT Access and Service Account Credentials | provided by the [Google Auth Library](https://www.npmjs.com/package/google-auth-library) | provided by the [Google Auth Library](https://www.npmjs.com/package/google-auth-library) +Interceptors | :heavy_check_mark: | :heavy_check_mark: +Connection Keepalives | :heavy_check_mark: | :heavy_check_mark: +HTTP Connect Support | :heavy_check_mark: | :heavy_check_mark: +Retries | :heavy_check_mark: (without hedging) | :heavy_check_mark: (including hedging) +Stats/tracing/monitoring | :heavy_check_mark: | :x: +Load Balancing | :heavy_check_mark: | :heavy_check_mark: +Initial Metadata Options | :heavy_check_mark: | only `waitForReady` + +Other Properties | `grpc` | `@grpc/grpc-js` +-----------------|--------|---------------- +Pure JavaScript Code | :x: | :heavy_check_mark: +Supported Node Versions | >= 4 and <=14 | ^8.13.0 or >=10.10.0 +Supported Electron Versions | <=11.2 | >= 3 +Supported Platforms | Linux, Windows, MacOS | All +Supported Architectures | x86, x86-64, ARM7+ | All + +In addition, all channel arguments defined in [this header file](https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/grpc_types.h) are handled by the `grpc` library. +Of those, a subset are handled by the `@grpc/grpc-js` library. See [the README](https://github.com/grpc/grpc-node/blob/master/packages/grpc-js/README.md#supported-channel-options) for `@grpc/grpc-js` for the list of supported channel options. diff --git a/README.md b/README.md index bd8826a83..f0f215ce3 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,37 @@ ## Implementations -### C-based Client and Server +For a comparison of the features available in these two libraries, see [this document](https://github.com/grpc/grpc-node/tree/master/PACKAGE-COMPARISON.md) -Directory: [`packages/grpc-native-core`](https://github.com/grpc/grpc-node/tree/master/packages/grpc-native-core) (see here for installation information) +### Pure JavaScript Client and Server -npm package: [grpc](https://www.npmjs.com/package/grpc). +Directory: [`packages/grpc-js`](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) + +npm package: [@grpc/grpc-js](https://www.npmjs.com/package/@grpc/grpc-js) -This is the existing, feature-rich implementation of gRPC using a C++ addon. It works on all LTS versions of Node.js on most platforms that Node.js runs on. +This library implements the core functionality of gRPC purely in JavaScript, without a C++ addon. It works on the latest versions of Node.js on all platforms that Node.js runs on. -### Pure JavaScript Client +### C-based Client and Server (deprecated) -Directory: [`packages/grpc-js-core`](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js-core) +Directory: [`packages/grpc-native-core`](https://github.com/grpc/grpc-node/tree/grpc@1.24.x/packages/grpc-native-core) (lives in the `grpc@1.24.x` branch) (see here for installation information) -**This library is currently incomplete and experimental, built on the [experimental http2 Node module](https://nodejs.org/api/http2.html).** +npm package: [grpc](https://www.npmjs.com/package/grpc). -This library implements the core functionality of gRPC purely in JavaScript, without a C++ addon. It works on the latest version of Node.js (with the `--expose-http2` flag set) on all platforms that Node.js runs on. +This is the deprecated implementation of gRPC using a C++ addon. It works on versions of Node.js up to 14 on most platforms that Node.js runs on. ## Other Packages +### gRPC Protobuf Loader + +Directory: [`packages/proto-loader`](https://github.com/grpc/grpc-node/tree/master/packages/proto-loader) + +npm package: [@grpc/proto-loader](https://www.npmjs.com/package/@grpc/proto-loader) + +This library loads `.proto` files into objects that can be passed to the gRPC libraries. + ### gRPC Tools -Directory: `packages/grpc-tools` +Directory: [`packages/grpc-tools`](https://github.com/grpc/grpc-node/tree/master/packages/grpc-tools) npm package: [grpc-tools](https://www.npmjs.com/package/grpc-tools) @@ -31,7 +41,7 @@ Distribution of protoc and the gRPC Node protoc plugin for ease of installation ### gRPC Health Check Service -Directory: `packages/grpc-health-check` +Directory: [`packages/grpc-health-check`](https://github.com/grpc/grpc-node/tree/master/packages/grpc-health-check) npm package: [grpc-health-check](https://www.npmjs.com/package/grpc-health-check) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..be6e10870 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +For information on gRPC Security Policy and reporting potentional security issues, please see [gRPC CVE Process](https://github.com/grpc/proposal/blob/master/P4-grpc-cve-process.md). diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 000000000..bfcf94edc --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,38 @@ +# Troubleshooting grpc-js + +This guide is for troubleshooting the `grpc-js` library for Node.js + +## Enabling extra logging and tracing + +Extra logging can be very useful for diagnosing problems. `grpc-js` supports +the `GRPC_VERBOSITY` and `GRPC_TRACE` environment variables that can be used to increase the amount of information +that gets printed to stderr. + +## GRPC_VERBOSITY + +`GRPC_VERBOSITY` is used to set the minimum level of log messages printed by gRPC (supported values are `DEBUG`, `INFO` and `ERROR`). If this environment variable is unset, only `ERROR` logs will be printed. + +## GRPC_TRACE + +`GRPC_TRACE` can be used to enable extra logging for some internal gRPC components. Enabling the right traces can be invaluable +for diagnosing for what is going wrong when things aren't working as intended. Possible values for `GRPC_TRACE` are listed in [Environment Variables Overview](doc/environment_variables.md). +Multiple traces can be enabled at once (use comma as separator). + +``` +# Enable debug logs for an application +GRPC_VERBOSITY=debug ./helloworld_application_using_grpc +``` + +``` +# Print information about channel state changes +GRPC_VERBOSITY=debug GRPC_TRACE=connectivity_state ./helloworld_application_using_grpc +``` + +``` +# Print info from 3 different tracers, including tracing logs with log level DEBUG +GRPC_VERBOSITY=debug GRPC_TRACE=channel,subchannel,call_stream ./helloworld_application_using_grpc +``` + +Please note that the `GRPC_TRACE` environment variable has nothing to do with gRPC's "tracing" feature (= tracing RPCs in +microservice environment to gain insight about how requests are processed by deployment), it is merely used to enable printing +of extra logs. diff --git a/doc/compression.md b/doc/compression.md new file mode 100644 index 000000000..bfe325a08 --- /dev/null +++ b/doc/compression.md @@ -0,0 +1,31 @@ +# Compression + +## Client side +The preferred method for configuring message compression on a client is to pass `options` when the client object is instantiated. + +These two options control compression behavior: + +**grpc.default_compression_algorithm** (int) + +Default compression algorithm for the channel, applies to sending messages. + +Possible values for this option are: +- `0` - No compression +- `1` - Compress with DEFLATE algorithm +- `2` - Compress with GZIP algorithm +- `3` - Stream compression with GZIP algorithm + +**grpc.default_compression_level** (int) + +Default compression level for the channel, applies to receiving messages. + +Possible values for this option are: +- `0` - None +- `1` - Low level +- `2` - Medium level +- `3` - High level + +### Code example +```javascript +client = new ExampleClient("example.com", credentials.createInsecure(), {'grpc.default_compression_algorithm': 2, 'grpc.default_compression_level': 2}); +``` diff --git a/doc/environment_variables.md b/doc/environment_variables.md new file mode 100644 index 000000000..70b32e715 --- /dev/null +++ b/doc/environment_variables.md @@ -0,0 +1,65 @@ +# grpc-js environment variables + +`@grpc/grpc-js` exposes some configuration as environment variables that +can be set. + +*For the legacy `grpc` library, the environment variables are documented +[in the main gRPC repository](https://github.com/grpc/grpc/blob/master/doc/environment_variables.md)* + +* grpc_proxy, https_proxy, http_proxy + The URI of the proxy to use for HTTP CONNECT support. These variables are + checked in order, and the first one that has a value is used. + +* no_grpc_proxy, no_proxy + A comma separated list of hostnames to connect to without using a proxy even + if a proxy is set. These variables are checked in order, and the first one + that has a value is used. + +* GRPC_SSL_CIPHER_SUITES + A colon separated list of cipher suites to use with OpenSSL + Defaults to the defaults for Node.js + +* GRPC_DEFAULT_SSL_ROOTS_FILE_PATH + PEM file to load SSL roots from + +* GRPC_NODE_TRACE, GRPC_TRACE + A comma separated list of tracers that provide additional insight into how + grpc-js is processing requests via debug logs. Available tracers include: + - `call_stream` - Traces client request internals + - `channel` - Traces channel events + - `connectivity_state` - Traces channel connectivity state changes + - `dns_resolver` - Traces DNS resolution + - `ip_resolver` - Traces IPv4/v6 resolution + - `pick_first` - Traces the pick first load balancing policy + - `proxy` - Traces proxy operations + - `resolving_load_balancer` - Traces the resolving load balancer + - `round_robin` - Traces the round robin load balancing policy + - `server` - Traces high-level server events + - `server_call` - Traces server handling of individual requests + - `subchannel` - Traces subchannel connectivity state and errors + - `subchannel_refcount` - Traces subchannel refcount changes. Includes per-call logs. + - `subchannel_flowctrl` - Traces HTTP/2 flow control. Includes per-call logs. + - `subchannel_internals` - Traces HTTP/2 session state. Includes per-call logs. + - `channel_stacktrace` - Traces channel construction events with stack traces. + - `keepalive` - Traces gRPC keepalive pings + - `index` - Traces module loading + - `outlier_detection` - Traces outlier detection events + + The following tracers are added by the `@grpc/grpc-js-xds` library: + - `cds_balancer` - Traces the CDS load balancing policy + - `eds_balancer` - Traces the EDS load balancing policy + - `priority` - Traces the priority load balancing policy + - `weighted_target` - Traces the weighted target load balancing policy + - `xds_client` - Traces the xDS Client + - `xds_cluster_manager` - Traces the xDS cluster manager load balancing policy + - `xds_resolver` - Traces the xDS name resolver + + 'all' can additionally be used to turn all traces on. + Individual traces can be disabled by prefixing them with '-'. + +* GRPC_NODE_VERBOSITY, GRPC_VERBOSITY + Default gRPC logging verbosity - one of: + - DEBUG - log all gRPC messages + - INFO - log INFO and ERROR message + - ERROR - log only errors (default) + - NONE - won't log any \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index f1a333e47..000000000 --- a/gulpfile.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2017 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. - * - */ - -const _gulp = require('gulp'); -const help = require('gulp-help'); - -// gulp-help monkeypatches tasks to have an additional description parameter -const gulp = help(_gulp); - -require('./packages/grpc-health-check/gulpfile'); -require('./packages/grpc-js-core/gulpfile'); -require('./packages/grpc-native-core/gulpfile'); -require('./test/gulpfile'); - -const root = __dirname; - -gulp.task('install.all', 'Install dependencies for all subdirectory packages', - ['js.core.install', 'native.core.install', 'health-check.install', 'internal.test.install']); - -gulp.task('install.all.windows', 'Install dependencies for all subdirectory packages for MS Windows', - ['js.core.install', 'native.core.install.windows', 'health-check.install', 'internal.test.install']); - -gulp.task('lint', 'Emit linting errors in source and test files', - ['js.core.lint', 'native.core.lint']); - -gulp.task('build', 'Build packages', ['js.core.compile', 'native.core.build']); - -gulp.task('link.create', 'Initialize npm links to packages', - ['native.core.link.create']); - -gulp.task('link.only', 'Link packages together without rebuilding anything', - ['health-check.link.add', 'internal.test.link.add']); - -gulp.task('link', 'Link local packages together after building', - ['link.create'], () => { - gulp.start('link.only'); - }); - -gulp.task('setup', 'One-time setup for a clean repository', ['install.all'], () => { - gulp.start('link'); -}); -gulp.task('setup.windows', 'One-time setup for a clean repository for MS Windows', ['install.all.windows'], () => { - gulp.start('link'); -}); - -gulp.task('clean', 'Delete generated files', ['js.core.clean', 'native.core.clean']); - -gulp.task('clean.all', 'Delete all files created by tasks', - ['js.core.clean.all', 'native.core.clean.all', 'health-check.clean.all', - 'internal.test.clean.all']); - -gulp.task('native.test.only', 'Run tests of native code without rebuilding anything', - ['native.core.test', 'internal.test.test', 'health-check.test']); - -gulp.task('native.test', 'Run tests of native code', ['build'], () => { - gulp.start('native.test.only'); -}); - -gulp.task('test.only', 'Run tests without rebuilding anything', - ['js.core.test', 'native.test.only']); - -gulp.task('test', 'Run all tests', ['build'], () => { - gulp.start('test.only'); -}); - -gulp.task('doc.gen', 'Generate documentation', ['native.core.doc.gen']); - -gulp.task('default', ['help']); diff --git a/gulpfile.ts b/gulpfile.ts new file mode 100644 index 000000000..7ac4e9a0b --- /dev/null +++ b/gulpfile.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2017 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. + * + */ + +import * as gulp from 'gulp'; +import * as healthCheck from './packages/grpc-health-check/gulpfile'; +import * as jsCore from './packages/grpc-js/gulpfile'; +import * as jsXds from './packages/grpc-js-xds/gulpfile'; +import * as protobuf from './packages/proto-loader/gulpfile'; +import * as internalTest from './test/gulpfile'; + +const installAll = gulp.series(jsCore.install, healthCheck.install, protobuf.install, internalTest.install, jsXds.install); + +const lint = gulp.parallel(jsCore.lint); + +const build = gulp.series(jsCore.compile, protobuf.compile, jsXds.compile); + +const setup = gulp.series(installAll); + +const setupPureJSInterop = gulp.series(jsCore.install, protobuf.install, internalTest.install); + +const clean = gulp.series(jsCore.clean, protobuf.clean, jsXds.clean); + +const cleanAll = gulp.series(jsXds.cleanAll, jsCore.cleanAll, internalTest.cleanAll, protobuf.cleanAll); + +const nativeTestOnly = gulp.parallel(healthCheck.test); + +const nativeTest = gulp.series(build, nativeTestOnly); + +const testOnly = gulp.parallel(jsCore.test, nativeTestOnly, protobuf.test, jsXds.test); + +const test = gulp.series(build, testOnly, internalTest.test); + +export { + installAll, + lint, + build, + setup, + setupPureJSInterop, + clean, + cleanAll, + nativeTestOnly, + nativeTest, + test +}; diff --git a/install-nvm-windows.ps1 b/install-nvm-windows.ps1 deleted file mode 100644 index 709a1ec2d..000000000 --- a/install-nvm-windows.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2017 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. - -# We're going to store nvm-windows in the .\nvm directory. -$env:NVM_HOME = (Get-Item -Path ".\" -Verbose).FullName + "\nvm" - -# Downloading and unpacking nvm-windows -Invoke-WebRequest -Uri https://github.com/coreybutler/nvm-windows/releases/download/1.1.5/nvm-noinstall.zip -OutFile nvm-noinstall.zip -Add-Type -AssemblyName System.IO.Compression.FileSystem -[System.IO.Compression.ZipFile]::ExtractToDirectory("nvm-noinstall.zip", "nvm") - -$env:Path = $env:NVM_HOME + ";" + $env:Path -Out-File -Encoding "OEM" nvm\settings.txt -nvm root $env:NVM_HOME -"%*" | Out-File -Encoding "OEM" nvm\elevate.cmd diff --git a/merge_kokoro_logs.js b/merge_kokoro_logs.js index d8cc98db0..93f4d9c89 100644 --- a/merge_kokoro_logs.js +++ b/merge_kokoro_logs.js @@ -25,6 +25,30 @@ const readDir = util.promisify(fs.readdir); const rootDir = __dirname; +// Fake test suite log with a failure if log parsing failed +const parseFailureLog = [ + { + $: { + name: 'Unknown Test Suite', + tests: '1', + failures: '1', + }, + testcase: [ + { + $: { + classname: 'Test Log Parsing', + name: 'Test Log Parsing' + }, + failure: { + $: { + message: "Log parsing failed" + } + } + } + ] + } +]; + readDir(rootDir + '/reports') .then((dirNames) => Promise.all(dirNames.map((dirName) => @@ -40,8 +64,16 @@ readDir(rootDir + '/reports') )) ) .then((objects) => { - let merged = objects[0]; - merged.testsuites.testsuite = Array.prototype.concat.apply([], objects.map((obj) => obj.testsuites.testsuite)); + const merged = { + testsuites: { + testsuite: Array.prototype.concat.apply([], objects.map((obj) => { + if (obj) { + return obj.testsuites.testsuite; + } else { + return parseFailureLog; + }})) + } + } let builder = new xml2js.Builder(); let xml = builder.buildObject(merged); let resultName = path.resolve(rootDir, 'reports', dirName, 'sponge_log.xml'); diff --git a/package.json b/package.json index 2e9798ba7..49087fb05 100644 --- a/package.json +++ b/package.json @@ -9,31 +9,57 @@ }, "license": "Apache-2.0", "devDependencies": { + "@types/execa": "^0.8.0", + "@types/gulp": "^4.0.5", + "@types/gulp-mocha": "0.0.31", + "@types/ncp": "^2.0.1", "@types/node": "^8.0.32", + "@types/pify": "^3.0.0", + "@types/semver": "^5.5.0", + "coveralls": "^3.0.1", "del": "^3.0.0", "execa": "^0.8.0", - "gulp": "^3.9.1", - "gulp-help": "^1.6.1", - "gulp-jsdoc3": "^1.0.1", + "gulp": "^4.0.1", "gulp-jshint": "^2.0.4", "gulp-mocha": "^4.3.1", "gulp-sourcemaps": "^2.6.1", "gulp-tslint": "^8.1.1", "gulp-typescript": "^3.2.2", "gulp-util": "^3.0.8", - "jsdoc": "^3.3.2", + "jsdoc": "^4.0.3", "jshint": "^2.9.5", + "make-dir": "^1.1.0", "merge2": "^1.1.0", "mocha": "^3.5.3", "mocha-jenkins-reporter": "^0.3.9", + "ncp": "^2.0.0", + "nyc": "^11.7.2", + "pify": "^3.0.0", + "run-sequence": "^2.2.0", + "semver": "^5.5.0", + "symlink": "^2.1.0", "through2": "^2.0.3", + "ts-node": "^8.1.0", "tslint": "^5.5.0", - "typescript": "^2.5.1", + "typescript": "^3.7.2", "xml2js": "^0.4.19" }, "contributors": [ { "name": "Google Inc." } - ] + ], + "nyc": { + "include": [ + "packages/grpc-health-check/health.js", + "packages/grpc-js/build/src/*", + "packages/proto-loader/build/src/*" + ], + "cache": true, + "all": true + }, + "scripts": { + "test": "nyc gulp test", + "coverage": "nyc report --reporter=text-lcov | coveralls" + } } diff --git a/packages/grpc-health-check/LICENSE b/packages/grpc-health-check/LICENSE index 7750ce4fd..8dada3eda 100644 --- a/packages/grpc-health-check/LICENSE +++ b/packages/grpc-health-check/LICENSE @@ -1,13 +1,13 @@ - Apache License Version 2.0, January 2004 - https://www.apache.org/licenses/ + 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. @@ -86,6 +86,7 @@ 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: @@ -103,6 +104,7 @@ 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 @@ -120,7 +122,9 @@ 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, @@ -171,7 +175,18 @@ END OF TERMS AND CONDITIONS - Copyright 2015-2017 gRPC authors. + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/grpc-health-check/README.md b/packages/grpc-health-check/README.md new file mode 100644 index 000000000..62a88347f --- /dev/null +++ b/packages/grpc-health-check/README.md @@ -0,0 +1,60 @@ +# grpc-health-check + +Health check client and service for use with gRPC-node. + +## Background + +This package exports both a client and server that adhere to the [gRPC Health Checking Protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + +By using this package, clients and servers can rely on common proto and service definitions. This means: +- Clients can use the generated stubs to health check _any_ server that adheres to the protocol. +- Servers do not reimplement common logic for publishing health statuses. + +## Installation + +Use the package manager [npm](https://www.npmjs.com/get-npm) to install `grpc-health-check`. + +```bash +npm install grpc-health-check +``` + +## Usage + +### Server + +Any gRPC-node server can use `grpc-health-check` to adhere to the gRPC Health Checking Protocol. +The following shows how this package can be added to a pre-existing gRPC server. + +```javascript 1.8 +// Import package +let health = require('grpc-health-check'); + +// Define service status map. Key is the service name, value is the corresponding status. +// By convention, the empty string "" key represents that status of the entire server. +const statusMap = { + "ServiceFoo": proto.grpc.health.v1.HealthCheckResponse.ServingStatus.SERVING, + "ServiceBar": proto.grpc.health.v1.HealthCheckResponse.ServingStatus.NOT_SERVING, + "": proto.grpc.health.v1.HealthCheckResponse.ServingStatus.NOT_SERVING, +}; + +// Construct the service implementation +let healthImpl = new health.Implementation(statusMap); + +// Add the service and implementation to your pre-existing gRPC-node server +server.addService(health.service, healthImpl); +``` + +Congrats! Your server now allows any client to run a health check against it. + +### Client + +Any gRPC-node client can use `grpc-health-check` to run health checks against other servers that follow the protocol. + +## Contributing + +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +Please make sure to update tests as appropriate. + +## License +[Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) diff --git a/packages/grpc-health-check/gulpfile.js b/packages/grpc-health-check/gulpfile.js deleted file mode 100644 index 6e41de5e4..000000000 --- a/packages/grpc-health-check/gulpfile.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2017 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. - * - */ - -const _gulp = require('gulp'); -const help = require('gulp-help'); -const mocha = require('gulp-mocha'); -const execa = require('execa'); -const path = require('path'); -const del = require('del'); - -const gulp = help(_gulp); - -const healthCheckDir = __dirname; -const baseDir = path.resolve(healthCheckDir, '..', '..'); -const testDir = path.resolve(healthCheckDir, 'test'); - -gulp.task('health-check.clean.links', 'Delete npm links', () => { - return del(path.resolve(healthCheckDir, 'node_modules/grpc')); -}); - -gulp.task('health-check.clean.all', 'Delete all code created by tasks', - ['health-check.clean.links']); - -gulp.task('health-check.install', 'Install health check dependencies', () => { - return execa('npm', ['install', '--unsafe-perm'], {cwd: healthCheckDir, stdio: 'inherit'}); -}); - -gulp.task('health-check.link.add', 'Link local copy of grpc', ['health-check.install'], () => { - return execa('npm', ['link', 'grpc'], {cwd: healthCheckDir, stdio: 'inherit'}); -}); - -gulp.task('health-check.test', 'Run health check tests', - () => { - return gulp.src(`${testDir}/*.js`).pipe(mocha({reporter: 'mocha-jenkins-reporter'})); - }); diff --git a/packages/grpc-health-check/gulpfile.ts b/packages/grpc-health-check/gulpfile.ts new file mode 100644 index 000000000..0ddaa257e --- /dev/null +++ b/packages/grpc-health-check/gulpfile.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2019 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. + * + */ + +import * as gulp from 'gulp'; +import * as mocha from 'gulp-mocha'; +import * as execa from 'execa'; +import * as path from 'path'; +import * as del from 'del'; +import {linkSync} from '../../util'; + +const healthCheckDir = __dirname; +const baseDir = path.resolve(healthCheckDir, '..', '..'); +const testDir = path.resolve(healthCheckDir, 'test'); + +const runInstall = () => execa('npm', ['install', '--unsafe-perm'], {cwd: healthCheckDir, stdio: 'inherit'}); + +const runRebuild = () => execa('npm', ['rebuild', '--unsafe-perm'], {cwd: healthCheckDir, stdio: 'inherit'}); + +const install = gulp.series(runInstall, runRebuild); + +const test = () => gulp.src(`${testDir}/*.js`).pipe(mocha({reporter: 'mocha-jenkins-reporter'})); + +export { + install, + test +} \ No newline at end of file diff --git a/packages/grpc-health-check/health.js b/packages/grpc-health-check/health.js index 9b1cfbd05..cfa9c8348 100644 --- a/packages/grpc-health-check/health.js +++ b/packages/grpc-health-check/health.js @@ -20,13 +20,14 @@ var grpc = require('grpc'); -var _ = require('lodash'); +var _get = require('lodash.get'); +var _clone = require('lodash.clone') var health_messages = require('./v1/health_pb'); var health_service = require('./v1/health_grpc_pb'); function HealthImplementation(statusMap) { - this.statusMap = _.clone(statusMap); + this.statusMap = _clone(statusMap); } HealthImplementation.prototype.setStatus = function(service, status) { @@ -35,7 +36,7 @@ HealthImplementation.prototype.setStatus = function(service, status) { HealthImplementation.prototype.check = function(call, callback){ var service = call.request.getService(); - var status = _.get(this.statusMap, service, null); + var status = _get(this.statusMap, service, null); if (status === null) { // TODO(murgatroid99): Do this without an explicit reference to grpc. callback({code:grpc.status.NOT_FOUND}); @@ -48,6 +49,7 @@ HealthImplementation.prototype.check = function(call, callback){ module.exports = { Client: health_service.HealthClient, + messages: health_messages, service: health_service.HealthService, Implementation: HealthImplementation }; diff --git a/packages/grpc-health-check/package.json b/packages/grpc-health-check/package.json index 41a318bc4..e9b836346 100644 --- a/packages/grpc-health-check/package.json +++ b/packages/grpc-health-check/package.json @@ -1,8 +1,8 @@ { "name": "grpc-health-check", - "version": "1.7.0-pre1", + "version": "1.8.0", "author": "Google Inc.", - "description": "Health check service for use with gRPC", + "description": "Health check client and service for use with gRPC-node", "repository": { "type": "git", "url": "https://github.com/grpc/grpc-node.git" @@ -15,12 +15,14 @@ } ], "dependencies": { - "google-protobuf": "^3.0.0", + "google-protobuf": "^3.4.0", "grpc": "^1.6.0", - "lodash": "^3.9.3" + "lodash.clone": "^4.5.0", + "lodash.get": "^4.4.2" }, "files": [ "LICENSE", + "README.md", "health.js", "v1" ], diff --git a/packages/grpc-health-check/v1/health_grpc_pb.js b/packages/grpc-health-check/v1/health_grpc_pb.js index 2a1ac6ff7..63de213e2 100644 --- a/packages/grpc-health-check/v1/health_grpc_pb.js +++ b/packages/grpc-health-check/v1/health_grpc_pb.js @@ -23,7 +23,7 @@ function serialize_HealthCheckRequest(arg) { if (!(arg instanceof v1_health_pb.HealthCheckRequest)) { throw new Error('Expected argument of type HealthCheckRequest'); } - return new Buffer(arg.serializeBinary()); + return Buffer.from(arg.serializeBinary()); } function deserialize_HealthCheckRequest(buffer_arg) { @@ -34,7 +34,7 @@ function serialize_HealthCheckResponse(arg) { if (!(arg instanceof v1_health_pb.HealthCheckResponse)) { throw new Error('Expected argument of type HealthCheckResponse'); } - return new Buffer(arg.serializeBinary()); + return Buffer.from(arg.serializeBinary()); } function deserialize_HealthCheckResponse(buffer_arg) { diff --git a/packages/grpc-js-core/gulpfile.js b/packages/grpc-js-core/gulpfile.js deleted file mode 100644 index 79592139a..000000000 --- a/packages/grpc-js-core/gulpfile.js +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2017 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. - * - */ - -'use strict'; - -const _gulp = require('gulp'); -const help = require('gulp-help'); - -// gulp-help monkeypatches tasks to have an additional description parameter -const gulp = help(_gulp); - -const del = require('del'); -const mocha = require('gulp-mocha'); -const sourcemaps = require('gulp-sourcemaps'); -const tslint = require('gulp-tslint'); -const typescript = require('gulp-typescript'); -const util = require('gulp-util'); -const merge2 = require('merge2'); -const path = require('path'); -const through = require('through2'); -const execa = require('execa'); - -Error.stackTraceLimit = Infinity; - -const jsCoreDir = __dirname; -const tslintPath = path.resolve(jsCoreDir, 'node_modules/google-ts-style/tslint.json'); -const tsconfigPath = path.resolve(jsCoreDir, 'tsconfig.json'); -const outDir = path.resolve(jsCoreDir, 'build'); -const srcDir = path.resolve(jsCoreDir, 'src'); -const testDir = path.resolve(jsCoreDir, 'test'); - -function onError() {} - -// Coalesces all specified --file parameters into a single array -const files = !util.env.file ? [] : - Array.isArray(util.env.file) ? util.env.file : [util.env.file]; - -// If --dev is passed, override certain ts config options -let tsDevOptions = {}; -if (util.env.dev) { - tsDevOptions = { - allowUnreachableCode: true, - noUnusedParameters: false, - noImplicitAny: false, - noImplicitThis: false, - noEmitOnError: false - }; -} - -/** - * Helper function that creates a gulp task function that opens files in a - * directory that match a certain glob pattern, transpiles them, and writes them - * to an output directory. - * @param {Object} globs - * @param {string=} globs.transpile The glob pattern for files to transpile. - * Defaults to match all *.ts files in baseDir (incl. subdirectories). - * @param {string=} globs.copy The glob pattern for files to transpile. - * Defaults to match all but *.ts files in baseDir (incl. subdirectories). - * @return A gulp task function. - */ -function makeCompileFn(globs) { - const transpileGlob = globs.transpile || `${srcDir}/**/*.ts`; - const copyGlob = globs.copy || '!(**/*)'; - return () => { - const tsProject = typescript.createProject(tsconfigPath, tsDevOptions)(); - const data = gulp.src(transpileGlob, { base: jsCoreDir }) - .pipe(sourcemaps.init()) - .pipe(tsProject) - .on('error', onError); - const dts = data.dts; - const js = data.js; - const jsmap = js.pipe(sourcemaps.write('.', { - includeContent: false, - sourceRoot: '..' - })); - const copy = gulp.src(copyGlob, { base: jsCoreDir }); - return merge2([ - js.pipe(gulp.dest(`${outDir}`)), - dts.pipe(gulp.dest(`${outDir}/types`)), - jsmap.pipe(gulp.dest(`${outDir}`)), - copy.pipe(gulp.dest(`${outDir}`)) - ]); - }; -} - -gulp.task('js.core.install', 'Install native core dependencies', () => { - return execa('npm', ['install', '--unsafe-perm'], {cwd: jsCoreDir, stdio: 'inherit'}); -}); - -/** - * Runs tslint on files in src/, with linting rules defined in tslint.json. - */ -gulp.task('js.core.lint', 'Emits linting errors found in src/ and test/.', () => { - const program = require('tslint').Linter.createProgram(tsconfigPath); - gulp.src([`${srcDir}/**/*.ts`, `${testDir}/**/*.ts`]) - .pipe(tslint({ - configuration: tslintPath, - formatter: 'codeFrame', - program - })) - .pipe(tslint.report()) - .on('warning', onError); -}); - -gulp.task('js.core.clean', 'Deletes transpiled code.', () => { - return del(outDir); -}); - -gulp.task('js.core.clean.all', 'Deletes all files added by targets', - ['js.core.clean']); - -/** - * Transpiles TypeScript files in src/ to JavaScript according to the settings - * found in tsconfig.json. - * Currently, all errors are emitted twice. This is being tracked here: - * https://github.com/ivogabe/gulp-typescript/issues/438 - */ -gulp.task('js.core.compile', 'Transpiles src/.', - makeCompileFn({ transpile: [`${srcDir}/**/*.ts`] })); - -/** - * Transpiles TypeScript files in both src/ and test/. - */ -gulp.task('js.core.test.compile', 'After dep tasks, transpiles test/.', ['js.core.compile'], - makeCompileFn({ transpile: [`${testDir}/**/*.ts`], copy: `${testDir}/**/!(*.ts)` })); - -/** - * Transpiles src/ and test/, and then runs all tests. - */ -gulp.task('js.core.test', 'After dep tasks, runs all tests.', - ['js.core.test.compile'], () => { - return gulp.src(`${outDir}/test/**/*.js`) - .pipe(mocha({reporter: 'mocha-jenkins-reporter'})); - } - ); - -/** - * Transpiles individual files, specified by the --file flag. - */ -gulp.task('js.core.compile.single', 'Transpiles individual files specified by --file.', - makeCompileFn({ - transpile: files.map(f => path.relative('.', f)) - }) - ); - -/** - * Run individual tests, specified by their pre-transpiled source path (as - * supplied through the '--file' flag). This is intended to be used as part of a - * VS Code "Gulp task" launch configuration; setting the "args" field to - * ["test.single", "--file", "${file}"] makes it possible for one to debug the - * currently open TS mocha test file in one step. - */ -gulp.task('js.core.test.single', 'After dep tasks, runs individual files specified ' + - 'by --file.', ['js.core.compile', 'js.core.compile.single'], () => { - // util.env contains CLI arguments for the gulp task. - // Determine the path to the transpiled version of this TS file. - const getTranspiledPath = (file) => { - const dir = path.dirname(path.relative(jsCoreDir, file)); - const basename = path.basename(file, '.ts'); - const result = `${outDir}/${dir}/${basename}.js`; - console.log(result); - return result; - }; - // Construct an instance of Mocha's runner API and feed it the path to the - // transpiled source. - return gulp.src(files.map(getTranspiledPath)) - .pipe(through.obj((file, enc, cb) => { - // Construct a new Mocha runner instance. - const Mocha = require('mocha'); - const runner = new Mocha(); - // Add the path to the test file to debug. - runner.addFile(file.path); - // Run the test suite. - runner.run((failures) => { - if (failures > 0) { - cb(new Error(`Mocha: ${failures} failures in ${file.path}]`)); - } else { - cb(null); - } - }); - })); - } - ); diff --git a/packages/grpc-js-core/package.json b/packages/grpc-js-core/package.json deleted file mode 100644 index 83fcb0e9a..000000000 --- a/packages/grpc-js-core/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "grpc-js-core", - "version": "0.1.0", - "description": "gRPC Library for Node - pure JS core", - "homepage": "https://grpc.io/", - "main": "build/src/index.js", - "private": true, - "engines": { - "node": ">=8.4" - }, - "keywords": [], - "author": { - "name": "Google Inc." - }, - "types": "src/index.ts", - "license": "Apache-2.0", - "devDependencies": { - "@types/lodash": "^4.14.73", - "@types/mocha": "^2.2.42", - "@types/node": "^8.0.25", - "clang-format": "^1.0.53", - "google-ts-style": "^0.2.0" - }, - "contributors": [ - { - "name": "Google Inc." - } - ], - "_id": "grpc-js-core@0.1.0", - "scripts": { - "build": "npm run compile", - "clean": "gulp clean", - "compile": "gulp js.core.compile", - "format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts", - "lint": "tslint -c node_modules/google-ts-style/tslint.json -p . -t codeFrame --type-check", - "prepare": "npm run build", - "test": "gulp test" - }, - "dependencies": { - "lodash": "^4.17.4" - }, - "files": [ - "build/src/*.js" - ] -} diff --git a/packages/grpc-js-core/src/call-credentials-filter.ts b/packages/grpc-js-core/src/call-credentials-filter.ts deleted file mode 100644 index 678124592..000000000 --- a/packages/grpc-js-core/src/call-credentials-filter.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {promisify} from 'util'; - -import {CallCredentials} from './call-credentials'; -import {CallStream} from './call-stream'; -import {Http2Channel} from './channel'; -import {BaseFilter, Filter, FilterFactory} from './filter'; -import {Metadata} from './metadata'; - -export class CallCredentialsFilter extends BaseFilter implements Filter { - constructor(private readonly credentials: CallCredentials) { - super(); - } - - async sendMetadata(metadata: Promise): Promise { - // TODO(murgatroid99): pass real options to generateMetadata - let credsMetadata = this.credentials.generateMetadata.bind({}); - let resultMetadata = await metadata; - resultMetadata.merge(await credsMetadata); - return resultMetadata; - } -} - -export class CallCredentialsFilterFactory implements - FilterFactory { - private readonly credentials: CallCredentials; - constructor(channel: Http2Channel) { - this.credentials = channel.credentials.getCallCredentials(); - } - - createFilter(callStream: CallStream): CallCredentialsFilter { - return new CallCredentialsFilter( - this.credentials.compose(callStream.getCredentials())); - } -} diff --git a/packages/grpc-js-core/src/call-credentials.ts b/packages/grpc-js-core/src/call-credentials.ts deleted file mode 100644 index 5b322402f..000000000 --- a/packages/grpc-js-core/src/call-credentials.ts +++ /dev/null @@ -1,92 +0,0 @@ -import {map, reduce} from 'lodash'; - -import {Metadata} from './metadata'; - -export type CallMetadataGenerator = - (options: Object, cb: (err: Error|null, metadata?: Metadata) => void) => - void; - -/** - * A class that represents a generic method of adding authentication-related - * metadata on a per-request basis. - */ -export interface CallCredentials { - /** - * Asynchronously generates a new Metadata object. - * @param options Options used in generating the Metadata object. - */ - generateMetadata(options: Object): Promise; - /** - * Creates a new CallCredentials object from properties of both this and - * another CallCredentials object. This object's metadata generator will be - * called first. - * @param callCredentials The other CallCredentials object. - */ - compose(callCredentials: CallCredentials): CallCredentials; -} - -class ComposedCallCredentials implements CallCredentials { - constructor(private creds: CallCredentials[]) {} - - async generateMetadata(options: Object): Promise { - let base: Metadata = new Metadata(); - let generated: Metadata[] = await Promise.all( - map(this.creds, (cred) => cred.generateMetadata(options))); - for (let gen of generated) { - base.merge(gen); - } - return base; - } - - compose(other: CallCredentials): CallCredentials { - return new ComposedCallCredentials(this.creds.concat([other])); - } -} - -class SingleCallCredentials implements CallCredentials { - constructor(private metadataGenerator: CallMetadataGenerator) {} - - async generateMetadata(options: Object): Promise { - return new Promise((resolve, reject) => { - this.metadataGenerator(options, (err, metadata) => { - if (metadata !== undefined) { - resolve(metadata); - } else { - reject(err); - } - }); - }); - } - - compose(other: CallCredentials): CallCredentials { - return new ComposedCallCredentials([this, other]); - } -} - -class EmptyCallCredentials implements CallCredentials { - async generateMetadata(options: Object): Promise { - return new Metadata(); - } - - compose(other: CallCredentials): CallCredentials { - return other; - } -} - -export namespace CallCredentials { - /** - * Creates a new CallCredentials object from a given function that generates - * Metadata objects. - * @param metadataGenerator A function that accepts a set of options, and - * generates a Metadata object based on these options, which is passed back - * to the caller via a supplied (err, metadata) callback. - */ - export function createFromMetadataGenerator( - metadataGenerator: CallMetadataGenerator): CallCredentials { - return new SingleCallCredentials(metadataGenerator); - } - - export function createEmpty(): CallCredentials { - return new EmptyCallCredentials(); - } -} diff --git a/packages/grpc-js-core/src/call-stream.ts b/packages/grpc-js-core/src/call-stream.ts deleted file mode 100644 index 51c587568..000000000 --- a/packages/grpc-js-core/src/call-stream.ts +++ /dev/null @@ -1,399 +0,0 @@ -import * as http2 from 'http2'; -import {Duplex} from 'stream'; - -import {CallCredentials} from './call-credentials'; -import {Status} from './constants'; -import {Filter} from './filter'; -import {FilterStackFactory} from './filter-stack'; -import {Metadata} from './metadata'; -import {ObjectDuplex} from './object-stream'; - -const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE} = http2.constants; - -export type Deadline = Date | number; - -export interface CallStreamOptions { - deadline: Deadline; - credentials: CallCredentials; - flags: number; -} - -export type CallOptions = Partial; - -export interface StatusObject { - code: Status; - details: string; - metadata: Metadata; -} - -export interface WriteObject { - message: Buffer; - flags?: number; -} - -/** - * This interface represents a duplex stream associated with a single gRPC call. - */ -export interface CallStream extends ObjectDuplex { - cancelWithStatus(status: Status, details: string): void; - getPeer(): string; - - getDeadline(): Deadline; - getCredentials(): CallCredentials; - /* If the return value is null, the call has not ended yet. Otherwise, it has - * ended with the specified status */ - getStatus(): StatusObject|null; - - addListener(event: string, listener: Function): this; - emit(event: string|symbol, ...args: any[]): boolean; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; - - addListener(event: 'metadata', listener: (metadata: Metadata) => void): this; - emit(event: 'metadata', metadata: Metadata): boolean; - on(event: 'metadata', listener: (metadata: Metadata) => void): this; - once(event: 'metadata', listener: (metadata: Metadata) => void): this; - prependListener(event: 'metadata', listener: (metadata: Metadata) => void): - this; - prependOnceListener( - event: 'metadata', listener: (metadata: Metadata) => void): this; - removeListener(event: 'metadata', listener: (metadata: Metadata) => void): - this; - - addListener(event: 'status', listener: (status: StatusObject) => void): this; - emit(event: 'status', status: StatusObject): boolean; - on(event: 'status', listener: (status: StatusObject) => void): this; - once(event: 'status', listener: (status: StatusObject) => void): this; - prependListener(event: 'status', listener: (status: StatusObject) => void): - this; - prependOnceListener( - event: 'status', listener: (status: StatusObject) => void): this; - removeListener(event: 'status', listener: (status: StatusObject) => void): - this; -} - -enum ReadState { - NO_DATA, - READING_SIZE, - READING_MESSAGE -} - -const emptyBuffer = Buffer.alloc(0); - -export class Http2CallStream extends Duplex implements CallStream { - public filterStack: Filter; - private statusEmitted = false; - private http2Stream: http2.ClientHttp2Stream|null = null; - private pendingRead = false; - private pendingWrite: Buffer|null = null; - private pendingWriteCallback: Function|null = null; - private pendingFinalCallback: Function|null = null; - - private readState: ReadState = ReadState.NO_DATA; - private readCompressFlag = false; - private readPartialSize: Buffer = Buffer.alloc(4); - private readSizeRemaining = 4; - private readMessageSize = 0; - private readPartialMessage: Buffer[] = []; - private readMessageRemaining = 0; - - private unpushedReadMessages: (Buffer|null)[] = []; - - // Status code mapped from :status. To be used if grpc-status is not received - private mappedStatusCode: Status = Status.UNKNOWN; - - // This is populated (non-null) if and only if the call has ended - private finalStatus: StatusObject|null = null; - - constructor( - private readonly methodName: string, - private readonly options: CallStreamOptions, - filterStackFactory: FilterStackFactory) { - super({objectMode: true}); - this.filterStack = filterStackFactory.createFilter(this); - } - - private endCall(status: StatusObject): void { - if (this.finalStatus === null) { - this.finalStatus = status; - this.emit('status', status); - } - } - - private tryPush(messageBytes: Buffer, canPush: boolean): boolean { - if (canPush) { - if (!this.push(messageBytes)) { - canPush = false; - (this.http2Stream as http2.ClientHttp2Stream).pause(); - } - } else { - this.unpushedReadMessages.push(messageBytes); - } - return canPush; - } - - attachHttp2Stream(stream: http2.ClientHttp2Stream): void { - if (this.finalStatus !== null) { - stream.rstWithCancel(); - } else { - this.http2Stream = stream; - stream.on('response', (headers) => { - switch (headers[HTTP2_HEADER_STATUS]) { - // TODO(murgatroid99): handle 100 and 101 - case '400': - this.mappedStatusCode = Status.INTERNAL; - break; - case '401': - this.mappedStatusCode = Status.UNAUTHENTICATED; - break; - case '403': - this.mappedStatusCode = Status.PERMISSION_DENIED; - break; - case '404': - this.mappedStatusCode = Status.UNIMPLEMENTED; - break; - case '429': - case '502': - case '503': - case '504': - this.mappedStatusCode = Status.UNAVAILABLE; - break; - default: - this.mappedStatusCode = Status.UNKNOWN; - } - delete headers[HTTP2_HEADER_STATUS]; - delete headers[HTTP2_HEADER_CONTENT_TYPE]; - let metadata: Metadata; - try { - metadata = Metadata.fromHttp2Headers(headers); - } catch (e) { - this.cancelWithStatus(Status.UNKNOWN, e.message); - return; - } - this.filterStack.receiveMetadata(Promise.resolve(metadata)) - .then( - (finalMetadata) => { - this.emit('metadata', finalMetadata); - }, - (error) => { - this.cancelWithStatus(Status.UNKNOWN, error.message); - }); - }); - stream.on('trailers', (headers) => { - let code: Status = this.mappedStatusCode; - if (headers.hasOwnProperty('grpc-status')) { - let receivedCode = Number(headers['grpc-status']); - if (receivedCode in Status) { - code = receivedCode; - } else { - code = Status.UNKNOWN; - } - delete headers['grpc-status']; - } - let details = ''; - if (headers.hasOwnProperty('grpc-message')) { - details = decodeURI(headers['grpc-message']); - } - let metadata: Metadata; - try { - metadata = Metadata.fromHttp2Headers(headers); - } catch (e) { - metadata = new Metadata(); - } - let status: StatusObject = {code, details, metadata}; - this.filterStack.receiveTrailers(Promise.resolve(status)) - .then( - (finalStatus) => { - this.endCall(finalStatus); - }, - (error) => { - this.endCall({ - code: Status.INTERNAL, - details: 'Failed to process received status', - metadata: new Metadata() - }); - }); - }); - stream.on('data', (data) => { - let readHead = 0; - let canPush = true; - let toRead: number; - while (readHead < data.length) { - switch (this.readState) { - case ReadState.NO_DATA: - this.readCompressFlag = (data.readUInt8(readHead) !== 0); - readHead += 1; - this.readState = ReadState.READING_SIZE; - this.readPartialSize.fill(0); - this.readSizeRemaining = 4; - this.readMessageSize = 0; - this.readMessageRemaining = 0; - this.readPartialMessage = []; - break; - case ReadState.READING_SIZE: - toRead = Math.min(data.length - readHead, this.readSizeRemaining); - data.copy( - this.readPartialSize, 4 - this.readSizeRemaining, readHead, - readHead + toRead); - this.readSizeRemaining -= toRead; - readHead += toRead; - // readSizeRemaining >=0 here - if (this.readSizeRemaining === 0) { - this.readMessageSize = this.readPartialSize.readUInt32BE(0); - this.readMessageRemaining = this.readMessageSize; - if (this.readMessageRemaining > 0) { - this.readState = ReadState.READING_MESSAGE; - } else { - canPush = this.tryPush(emptyBuffer, canPush); - this.readState = ReadState.NO_DATA; - } - } - break; - case ReadState.READING_MESSAGE: - toRead = - Math.min(data.length - readHead, this.readMessageRemaining); - this.readPartialMessage.push( - data.slice(readHead, readHead + toRead)); - this.readMessageRemaining -= toRead; - readHead += toRead; - // readMessageRemaining >=0 here - if (this.readMessageRemaining === 0) { - // At this point, we have read a full message - const messageBytes = Buffer.concat( - this.readPartialMessage, this.readMessageSize); - // TODO(murgatroid99): Add receive message filters - canPush = this.tryPush(messageBytes, canPush); - this.readState = ReadState.NO_DATA; - } - } - } - }); - stream.on('end', () => { - if (this.unpushedReadMessages.length === 0) { - this.push(null); - } else { - this.unpushedReadMessages.push(null); - } - }); - stream.on('streamClosed', (errorCode) => { - let code: Status; - let details = ''; - switch (errorCode) { - case http2.constants.NGHTTP2_REFUSED_STREAM: - code = Status.UNAVAILABLE; - break; - case http2.constants.NGHTTP2_CANCEL: - code = Status.CANCELLED; - break; - case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM: - code = Status.RESOURCE_EXHAUSTED; - details = 'Bandwidth exhausted'; - break; - case http2.constants.NGHTTP2_INADEQUATE_SECURITY: - code = Status.PERMISSION_DENIED; - details = 'Protocol not secure enough'; - break; - default: - code = Status.INTERNAL; - } - this.endCall({code: code, details: details, metadata: new Metadata()}); - }); - stream.on('error', () => { - this.endCall({ - code: Status.INTERNAL, - details: 'Internal HTTP2 error', - metadata: new Metadata() - }); - }); - if (!this.pendingRead) { - stream.pause(); - } - if (this.pendingWrite) { - if (!this.pendingWriteCallback) { - throw new Error('Invalid state in write handling code'); - } - stream.write(this.pendingWrite, this.pendingWriteCallback); - } - if (this.pendingFinalCallback) { - stream.end(this.pendingFinalCallback); - } - } - } - - cancelWithStatus(status: Status, details: string): void { - this.endCall({code: status, details: details, metadata: new Metadata()}); - if (this.http2Stream !== null) { - /* TODO(murgatroid99): Determine if we want to send different RST_STREAM - * codes based on the status code */ - this.http2Stream.rstWithCancel(); - } - } - - getDeadline(): Deadline { - return this.options.deadline; - } - - getCredentials(): CallCredentials { - return this.options.credentials; - } - - getStatus(): StatusObject|null { - return this.finalStatus; - } - - getPeer(): string { - throw new Error('Not yet implemented'); - } - - _read(size: number) { - if (this.http2Stream === null) { - this.pendingRead = true; - } else { - while (this.unpushedReadMessages.length > 0) { - const nextMessage = this.unpushedReadMessages.shift(); - const keepPushing = this.push(nextMessage); - if (nextMessage === null || (!keepPushing)) { - return; - } - } - /* Only resume reading from the http2Stream if we don't have any pending - * messages to emit, and we haven't gotten the signal to stop pushing - * messages */ - this.http2Stream.resume(); - } - } - - // Encode a message to the wire format - private encodeMessage(message: WriteObject): Buffer { - /* allocUnsafe doesn't initiate the bytes in the buffer. We are explicitly - * overwriting every single byte, so that should be fine */ - const output: Buffer = Buffer.allocUnsafe(message.message.length + 5); - // TODO(murgatroid99): handle compressed flag appropriately - output.writeUInt8(0, 0); - output.writeUInt32BE(message.message.length, 1); - message.message.copy(output, 5); - return output; - } - - _write(chunk: WriteObject, encoding: string, cb: Function) { - // TODO(murgatroid99): Add send message filters - const encodedMessage = this.encodeMessage(chunk); - if (this.http2Stream === null) { - this.pendingWrite = encodedMessage; - this.pendingWriteCallback = cb; - } else { - this.http2Stream.write(encodedMessage, cb); - } - } - - _final(cb: Function) { - if (this.http2Stream === null) { - this.pendingFinalCallback = cb; - } else { - this.http2Stream.end(cb); - } - } -} diff --git a/packages/grpc-js-core/src/call.ts b/packages/grpc-js-core/src/call.ts deleted file mode 100644 index ba38a41ca..000000000 --- a/packages/grpc-js-core/src/call.ts +++ /dev/null @@ -1,246 +0,0 @@ -import {EventEmitter} from 'events'; -import {Duplex, Readable, Writable} from 'stream'; - -import {CallStream, StatusObject, WriteObject} from './call-stream'; -import {Status} from './constants'; -import {Metadata} from './metadata'; -import {ObjectReadable, ObjectWritable} from './object-stream'; - -export interface ServiceError extends Error { - code?: number; - metadata?: Metadata; -} - -export class ServiceErrorImpl extends Error implements ServiceError { - code?: number; - metadata?: Metadata; -} - -export interface Call extends EventEmitter { - cancel(): void; - getPeer(): string; - - addListener(event: string, listener: Function): this; - emit(event: string|symbol, ...args: any[]): boolean; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; - - addListener(event: 'metadata', listener: (metadata: Metadata) => void): this; - emit(event: 'metadata', metadata: Metadata): boolean; - on(event: 'metadata', listener: (metadata: Metadata) => void): this; - once(event: 'metadata', listener: (metadata: Metadata) => void): this; - prependListener(event: 'metadata', listener: (metadata: Metadata) => void): - this; - prependOnceListener( - event: 'metadata', listener: (metadata: Metadata) => void): this; - removeListener(event: 'metadata', listener: (metadata: Metadata) => void): - this; -} - -export interface ClientUnaryCall extends Call {} - -export class ClientUnaryCallImpl extends EventEmitter implements Call { - constructor(private readonly call: CallStream) { - super(); - call.on('metadata', (metadata: Metadata) => { - this.emit('metadata', metadata); - }); - } - - cancel(): void { - this.call.cancelWithStatus(Status.CANCELLED, 'Cancelled on client'); - } - - getPeer(): string { - return this.call.getPeer(); - } -} - -export interface ClientReadableStream extends - Call, ObjectReadable { - deserialize: (chunk: Buffer) => ResponseType; - - addListener(event: string, listener: Function): this; - emit(event: string|symbol, ...args: any[]): boolean; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; - - addListener(event: 'status', listener: (status: StatusObject) => void): this; - emit(event: 'status', status: StatusObject): boolean; - on(event: 'status', listener: (status: StatusObject) => void): this; - once(event: 'status', listener: (status: StatusObject) => void): this; - prependListener(event: 'status', listener: (status: StatusObject) => void): - this; - prependOnceListener( - event: 'status', listener: (status: StatusObject) => void): this; - removeListener(event: 'status', listener: (status: StatusObject) => void): - this; -} - -export interface ClientWritableStream extends - Call, ObjectWritable { - serialize: (value: RequestType) => Buffer; - - addListener(event: string, listener: Function): this; - emit(event: string|symbol, ...args: any[]): boolean; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; -} - -export interface ClientDuplexStream extends - ClientWritableStream, ClientReadableStream { - addListener(event: string, listener: Function): this; - emit(event: string|symbol, ...args: any[]): boolean; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; -} - -function setUpReadableStream( - stream: ClientReadableStream, call: CallStream, - deserialize: (chunk: Buffer) => ResponseType): void { - call.on('data', (data: Buffer) => { - let deserialized: ResponseType; - try { - deserialized = deserialize(data); - } catch (e) { - call.cancelWithStatus(Status.INTERNAL, 'Failed to parse server response'); - return; - } - if (!stream.push(deserialized)) { - call.pause(); - } - }); - call.on('end', () => { - stream.push(null); - }); - call.on('status', (status: StatusObject) => { - stream.emit('status', status); - if (status.code !== Status.OK) { - const error = new ServiceErrorImpl(status.details); - error.code = status.code; - error.metadata = status.metadata; - stream.emit('error', error); - } - }); - call.pause(); -} - -export class ClientReadableStreamImpl extends Readable implements - ClientReadableStream { - constructor( - private readonly call: CallStream, - public readonly deserialize: (chunk: Buffer) => ResponseType) { - super({objectMode: true}); - call.on('metadata', (metadata: Metadata) => { - this.emit('metadata', metadata); - }); - setUpReadableStream(this, call, deserialize); - } - - cancel(): void { - this.call.cancelWithStatus(Status.CANCELLED, 'Cancelled on client'); - } - - getPeer(): string { - return this.call.getPeer(); - } - - _read(_size: number): void { - this.call.resume(); - } -} - -function tryWrite( - call: CallStream, serialize: (value: RequestType) => Buffer, - chunk: RequestType, encoding: string, cb: Function) { - let message: Buffer; - const flags: number = Number(encoding); - try { - message = serialize(chunk); - } catch (e) { - call.cancelWithStatus(Status.INTERNAL, 'Serialization failure'); - cb(e); - return; - } - const writeObj: WriteObject = {message: message}; - if (!Number.isNaN(flags)) { - writeObj.flags = flags; - } - call.write(writeObj, cb); -} - -export class ClientWritableStreamImpl extends Writable implements - ClientWritableStream { - constructor( - private readonly call: CallStream, - public readonly serialize: (value: RequestType) => Buffer) { - super({objectMode: true}); - call.on('metadata', (metadata: Metadata) => { - this.emit('metadata', metadata); - }); - } - - cancel(): void { - this.call.cancelWithStatus(Status.CANCELLED, 'Cancelled on client'); - } - - getPeer(): string { - return this.call.getPeer(); - } - - _write(chunk: RequestType, encoding: string, cb: Function) { - tryWrite(this.call, this.serialize, chunk, encoding, cb); - } - - _final(cb: Function) { - this.call.end(); - cb(); - } -} - -export class ClientDuplexStreamImpl extends Duplex - implements ClientDuplexStream { - constructor( - private readonly call: CallStream, - public readonly serialize: (value: RequestType) => Buffer, - public readonly deserialize: (chunk: Buffer) => ResponseType) { - super({objectMode: true}); - call.on('metadata', (metadata: Metadata) => { - this.emit('metadata', metadata); - }); - setUpReadableStream(this, call, deserialize); - } - - cancel(): void { - this.call.cancelWithStatus(Status.CANCELLED, 'Cancelled on client'); - } - - getPeer(): string { - return this.call.getPeer(); - } - - _read(_size: number): void { - this.call.resume(); - } - - _write(chunk: RequestType, encoding: string, cb: Function) { - tryWrite(this.call, this.serialize, chunk, encoding, cb); - } - - _final(cb: Function) { - this.call.end(); - cb(); - } -} diff --git a/packages/grpc-js-core/src/channel-credentials.ts b/packages/grpc-js-core/src/channel-credentials.ts deleted file mode 100644 index 0d87d5de1..000000000 --- a/packages/grpc-js-core/src/channel-credentials.ts +++ /dev/null @@ -1,116 +0,0 @@ -import {createSecureContext, SecureContext} from 'tls'; - -import {CallCredentials} from './call-credentials'; - -/** - * A class that contains credentials for communicating over a channel, as well - * as a set of per-call credentials, which are applied to every method call made - * over a channel initialized with an instance of this class. - */ -export interface ChannelCredentials { - /** - * Returns a copy of this object with the included set of per-call credentials - * expanded to include callCredentials. - * @param callCredentials A CallCredentials object to associate with this - * instance. - */ - compose(callCredentials: CallCredentials): ChannelCredentials; - - /** - * Gets the set of per-call credentials associated with this instance. - */ - getCallCredentials(): CallCredentials; - - /** - * Gets a SecureContext object generated from input parameters if this - * instance was created with createSsl, or null if this instance was created - * with createInsecure. - */ - getSecureContext(): SecureContext|null; -} - -abstract class ChannelCredentialsImpl implements ChannelCredentials { - protected callCredentials: CallCredentials; - - protected constructor(callCredentials?: CallCredentials) { - this.callCredentials = callCredentials || CallCredentials.createEmpty(); - } - - abstract compose(callCredentials: CallCredentials): ChannelCredentialsImpl; - - getCallCredentials(): CallCredentials { - return this.callCredentials; - } - - abstract getSecureContext(): SecureContext|null; -} - -class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { - constructor(callCredentials?: CallCredentials) { - super(callCredentials); - } - - compose(callCredentials: CallCredentials): ChannelCredentialsImpl { - throw new Error('Cannot compose insecure credentials'); - } - - getSecureContext(): SecureContext|null { - return null; - } -} - -class SecureChannelCredentialsImpl extends ChannelCredentialsImpl { - secureContext: SecureContext; - - constructor(secureContext: SecureContext, callCredentials?: CallCredentials) { - super(callCredentials); - this.secureContext = secureContext; - } - - compose(callCredentials: CallCredentials): ChannelCredentialsImpl { - const combinedCallCredentials = - this.callCredentials.compose(callCredentials); - return new SecureChannelCredentialsImpl( - this.secureContext, combinedCallCredentials); - } - - getSecureContext(): SecureContext|null { - return this.secureContext; - } -} - -export namespace ChannelCredentials { - /** - * Return a new ChannelCredentials instance with a given set of credentials. - * The resulting instance can be used to construct a Channel that communicates - * over TLS. - * @param rootCerts The root certificate data. - * @param privateKey The client certificate private key, if available. - * @param certChain The client certificate key chain, if available. - */ - export function createSsl( - rootCerts?: Buffer|null, privateKey?: Buffer|null, - certChain?: Buffer|null): ChannelCredentials { - if (privateKey && !certChain) { - throw new Error( - 'Private key must be given with accompanying certificate chain'); - } - if (!privateKey && certChain) { - throw new Error( - 'Certificate chain must be given with accompanying private key'); - } - const secureContext = createSecureContext({ - ca: rootCerts || undefined, - key: privateKey || undefined, - cert: certChain || undefined - }); - return new SecureChannelCredentialsImpl(secureContext); - } - - /** - * Return a new ChannelCredentials instance with no credentials. - */ - export function createInsecure(): ChannelCredentials { - return new InsecureChannelCredentialsImpl(); - } -} diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts deleted file mode 100644 index 1b7fda5b0..000000000 --- a/packages/grpc-js-core/src/channel.ts +++ /dev/null @@ -1,202 +0,0 @@ -import {EventEmitter} from 'events'; -import * as http2 from 'http2'; -import {SecureContext} from 'tls'; -import * as url from 'url'; - -import {CallCredentials} from './call-credentials'; -import {CallCredentialsFilterFactory} from './call-credentials-filter'; -import {CallOptions, CallStream, CallStreamOptions, Http2CallStream} from './call-stream'; -import {ChannelCredentials} from './channel-credentials'; -import {CompressionFilterFactory} from './compression-filter'; -import {Status} from './constants'; -import {DeadlineFilterFactory} from './deadline-filter'; -import {FilterStackFactory} from './filter-stack'; -import {Metadata, MetadataObject} from './metadata'; - -const IDLE_TIMEOUT_MS = 300000; - -const { - HTTP2_HEADER_AUTHORITY, - HTTP2_HEADER_CONTENT_TYPE, - HTTP2_HEADER_METHOD, - HTTP2_HEADER_PATH, - HTTP2_HEADER_SCHEME, - HTTP2_HEADER_TE -} = http2.constants; - -/** - * An interface that contains options used when initializing a Channel instance. - */ -export interface ChannelOptions { [index: string]: string|number; } - -export enum ConnectivityState { - CONNECTING, - READY, - TRANSIENT_FAILURE, - IDLE, - SHUTDOWN -} - -// todo: maybe we want an interface for load balancing, but no implementation -// for anything complicated - -/** - * An interface that represents a communication channel to a server specified - * by a given address. - */ -export interface Channel extends EventEmitter { - createStream(methodName: string, metadata: Metadata, options: CallOptions): - CallStream; - connect(callback: () => void): void; - getConnectivityState(): ConnectivityState; - close(): void; - - addListener(event: string, listener: Function): this; - emit(event: string|symbol, ...args: any[]): boolean; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; -} - -export class Http2Channel extends EventEmitter implements Channel { - private connectivityState: ConnectivityState = ConnectivityState.IDLE; - private idleTimerId: NodeJS.Timer|null = null; - /* For now, we have up to one subchannel, which will exist as long as we are - * connecting or trying to connect */ - private subChannel: http2.ClientHttp2Session|null; - private filterStackFactory: FilterStackFactory; - - private transitionToState(newState: ConnectivityState): void { - if (newState !== this.connectivityState) { - this.connectivityState = newState; - this.emit('connectivityStateChanged', newState); - } - } - - private startConnecting(): void { - this.transitionToState(ConnectivityState.CONNECTING); - let secureContext = this.credentials.getSecureContext(); - if (secureContext === null) { - this.subChannel = http2.connect(this.address); - } else { - this.subChannel = http2.connect(this.address, {secureContext}); - } - this.subChannel.once('connect', () => { - this.transitionToState(ConnectivityState.READY); - }); - this.subChannel.setTimeout(IDLE_TIMEOUT_MS, () => { - this.goIdle(); - }); - /* TODO(murgatroid99): add connection-level error handling with exponential - * reconnection backoff */ - } - - private goIdle(): void { - if (this.subChannel !== null) { - this.subChannel.shutdown({graceful: true}, () => undefined); - this.subChannel = null; - } - this.transitionToState(ConnectivityState.IDLE); - } - - private kickConnectivityState(): void { - if (this.connectivityState === ConnectivityState.IDLE) { - this.startConnecting(); - } - } - - constructor( - private readonly address: url.URL, - public readonly credentials: ChannelCredentials, - private readonly options: ChannelOptions) { - super(); - if (credentials.getSecureContext() === null) { - address.protocol = 'http'; - } else { - address.protocol = 'https'; - } - this.filterStackFactory = new FilterStackFactory([ - new CompressionFilterFactory(this), - new CallCredentialsFilterFactory(this), new DeadlineFilterFactory(this) - ]); - } - - private startHttp2Stream( - methodName: string, stream: Http2CallStream, metadata: Metadata) { - let finalMetadata: Promise = - stream.filterStack.sendMetadata(Promise.resolve(metadata)); - this.connect(() => { - finalMetadata.then( - (metadataValue) => { - let headers = metadataValue.toHttp2Headers(); - headers[HTTP2_HEADER_AUTHORITY] = this.address.hostname; - headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; - headers[HTTP2_HEADER_METHOD] = 'POST'; - headers[HTTP2_HEADER_PATH] = methodName; - headers[HTTP2_HEADER_TE] = 'trailers'; - if (stream.getStatus() === null) { - if (this.connectivityState === ConnectivityState.READY) { - let session: http2.ClientHttp2Session = - (this.subChannel as http2.ClientHttp2Session); - stream.attachHttp2Stream(session.request(headers)); - } else { - /* In this case, we lost the connection while finalizing - * metadata. That should be very unusual */ - setImmediate(() => { - this.startHttp2Stream(methodName, stream, metadata); - }); - } - } - }, - (error) => { - stream.cancelWithStatus( - Status.UNKNOWN, 'Failed to generate metadata'); - }); - }); - } - - createStream(methodName: string, metadata: Metadata, options: CallOptions): - CallStream { - if (this.connectivityState === ConnectivityState.SHUTDOWN) { - throw new Error('Channel has been shut down'); - } - let finalOptions: CallStreamOptions = { - deadline: options.deadline === undefined ? Infinity : options.deadline, - credentials: options.credentials || CallCredentials.createEmpty(), - flags: options.flags || 0 - }; - let stream: Http2CallStream = - new Http2CallStream(methodName, finalOptions, this.filterStackFactory); - this.startHttp2Stream(methodName, stream, metadata); - return stream; - } - - connect(callback: () => void): void { - this.kickConnectivityState(); - if (this.connectivityState === ConnectivityState.READY) { - setImmediate(callback); - } else { - this.once('connectivityStateChanged', (newState) => { - if (newState === ConnectivityState.READY) { - callback(); - } - }); - } - } - - getConnectivityState(): ConnectivityState { - return this.connectivityState; - } - - close() { - if (this.connectivityState === ConnectivityState.SHUTDOWN) { - throw new Error('Channel has been shut down'); - } - this.transitionToState(ConnectivityState.SHUTDOWN); - if (this.subChannel !== null) { - this.subChannel.shutdown({graceful: true}); - } - } -} diff --git a/packages/grpc-js-core/src/client.ts b/packages/grpc-js-core/src/client.ts deleted file mode 100644 index 59a84d24f..000000000 --- a/packages/grpc-js-core/src/client.ts +++ /dev/null @@ -1,267 +0,0 @@ -import {once} from 'lodash'; -import {URL} from 'url'; - -import {ClientDuplexStream, ClientDuplexStreamImpl, ClientReadableStream, ClientReadableStreamImpl, ClientUnaryCall, ClientUnaryCallImpl, ClientWritableStream, ClientWritableStreamImpl, ServiceError, ServiceErrorImpl} from './call'; -import {CallOptions, CallStream, StatusObject, WriteObject} from './call-stream'; -import {Channel, ChannelOptions, Http2Channel} from './channel'; -import {ChannelCredentials} from './channel-credentials'; -import {Status} from './constants'; -import {Metadata} from './metadata'; - -export interface UnaryCallback { - (err: ServiceError|null, value?: ResponseType): void; -} - -export class Client { - private readonly channel: Channel; - constructor( - address: string, credentials: ChannelCredentials, - options: ChannelOptions = {}) { - if (options['grpc.primary_user_agent']) { - options['grpc.primary_user_agent'] += ' '; - } else { - options['grpc.primary_user_agent'] = ''; - } - // TODO(murgatroid99): Figure out how to get version number - // options['grpc.primary_user_agent'] += 'grpc-node/' + version; - this.channel = new Http2Channel(new URL(address), credentials, options); - } - - close(): void { - this.channel.close(); - } - - waitForReady(deadline: Date|number, callback: (error: Error|null) => void): - void { - let cb: (error: Error|null) => void = once(callback); - let callbackCalled = false; - this.channel.connect(() => { - cb(null); - }); - if (deadline !== Infinity) { - let timeout: number; - let now: number = (new Date).getTime(); - if (deadline instanceof Date) { - timeout = deadline.getTime() - now; - } else { - timeout = deadline - now; - } - if (timeout < 0) { - timeout = 0; - } - setTimeout(() => { - cb(new Error('Failed to connect before the deadline')); - }, timeout); - } - } - - private handleUnaryResponse( - call: CallStream, deserialize: (value: Buffer) => ResponseType, - callback: UnaryCallback): void { - let responseMessage: ResponseType|null = null; - call.on('data', (data: Buffer) => { - if (responseMessage != null) { - call.cancelWithStatus(Status.INTERNAL, 'Too many responses received'); - } - try { - responseMessage = deserialize(data); - } catch (e) { - call.cancelWithStatus( - Status.INTERNAL, 'Failed to parse server response'); - } - }); - call.on('end', () => { - if (responseMessage == null) { - call.cancelWithStatus(Status.INTERNAL, 'Not enough responses received'); - } - }); - call.on('status', (status: StatusObject) => { - /* We assume that call emits status after it emits end, and that it - * accounts for any cancelWithStatus calls up until it emits status. - * Therefore, considering the above event handlers, status.code should be - * OK if and only if we have a non-null responseMessage */ - if (status.code === Status.OK) { - callback(null, responseMessage as ResponseType); - } else { - const error = new ServiceErrorImpl(status.details); - error.code = status.code; - error.metadata = status.metadata; - callback(error); - } - }); - } - - private checkOptionalUnaryResponseArguments( - arg1: Metadata|CallOptions|UnaryCallback, - arg2?: CallOptions|UnaryCallback, - arg3?: UnaryCallback): { - metadata: Metadata, - options: CallOptions, - callback: UnaryCallback - } { - if (arg1 instanceof Function) { - return {metadata: new Metadata(), options: {}, callback: arg1}; - } else if (arg2 instanceof Function) { - if (arg1 instanceof Metadata) { - return {metadata: arg1, options: {}, callback: arg2}; - } else { - return {metadata: new Metadata(), options: arg1, callback: arg2}; - } - } else { - if (!((arg1 instanceof Metadata) && (arg2 instanceof Object) && - (arg3 instanceof Function))) { - throw new Error('Incorrect arguments passed'); - } - return {metadata: arg1, options: arg2, callback: arg3}; - } - } - - makeUnaryRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, argument: RequestType, - metadata: Metadata, options: CallOptions, - callback: UnaryCallback): ClientUnaryCall; - makeUnaryRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, argument: RequestType, - metadata: Metadata, - callback: UnaryCallback): ClientUnaryCall; - makeUnaryRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, argument: RequestType, - options: CallOptions, - callback: UnaryCallback): ClientUnaryCall; - makeUnaryRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, argument: RequestType, - callback: UnaryCallback): ClientUnaryCall; - - makeUnaryRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, argument: RequestType, - metadata: Metadata|CallOptions|UnaryCallback, - options?: CallOptions|UnaryCallback, - callback?: UnaryCallback): ClientUnaryCall { - ({metadata, options, callback} = - this.checkOptionalUnaryResponseArguments( - metadata, options, callback)); - const call: CallStream = - this.channel.createStream(method, metadata, options); - const emitter: ClientUnaryCall = new ClientUnaryCallImpl(call); - const message: Buffer = serialize(argument); - const writeObj: WriteObject = {message: message}; - writeObj.flags = options.flags; - call.write(writeObj); - call.end(); - this.handleUnaryResponse(call, deserialize, callback); - return emitter; - } - - makeClientStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, metadata: Metadata, - options: CallOptions, - callback: UnaryCallback): ClientWritableStream; - makeClientStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, metadata: Metadata, - callback: UnaryCallback): ClientWritableStream; - makeClientStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, options: CallOptions, - callback: UnaryCallback): ClientWritableStream; - makeClientStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, - callback: UnaryCallback): ClientWritableStream; - - makeClientStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, - metadata: Metadata|CallOptions|UnaryCallback, - options?: CallOptions|UnaryCallback, - callback?: UnaryCallback): - ClientWritableStream { - ({metadata, options, callback} = - this.checkOptionalUnaryResponseArguments( - metadata, options, callback)); - const call: CallStream = - this.channel.createStream(method, metadata, options); - const stream: ClientWritableStream = - new ClientWritableStreamImpl(call, serialize); - this.handleUnaryResponse(call, deserialize, callback); - return stream; - } - - private checkMetadataAndOptions( - arg1?: Metadata|CallOptions, - arg2?: CallOptions): {metadata: Metadata, options: CallOptions} { - let metadata: Metadata; - let options: CallOptions; - if (arg1 instanceof Metadata) { - metadata = arg1; - if (arg2) { - options = arg2; - } else { - options = {}; - } - } else { - if (arg1) { - options = arg1; - } else { - options = {}; - } - metadata = new Metadata(); - } - return {metadata, options}; - } - - makeServerStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, argument: RequestType, - metadata: Metadata, - options?: CallOptions): ClientReadableStream; - makeServerStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, argument: RequestType, - options?: CallOptions): ClientReadableStream; - makeServerStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, argument: RequestType, - metadata?: Metadata|CallOptions, - options?: CallOptions): ClientReadableStream { - ({metadata, options} = this.checkMetadataAndOptions(metadata, options)); - const call: CallStream = - this.channel.createStream(method, metadata, options); - const stream: ClientReadableStream = - new ClientReadableStreamImpl(call, deserialize); - const message: Buffer = serialize(argument); - const writeObj: WriteObject = {message: message}; - writeObj.flags = options.flags; - call.write(writeObj); - call.end(); - return stream; - } - - makeBidiStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, metadata: Metadata, - options?: CallOptions): ClientDuplexStream; - makeBidiStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, - options?: CallOptions): ClientDuplexStream; - makeBidiStreamRequest( - method: string, serialize: (value: RequestType) => Buffer, - deserialize: (value: Buffer) => ResponseType, - metadata?: Metadata|CallOptions, - options?: CallOptions): ClientDuplexStream { - ({metadata, options} = this.checkMetadataAndOptions(metadata, options)); - const call: CallStream = - this.channel.createStream(method, metadata, options); - const stream: ClientDuplexStream = - new ClientDuplexStreamImpl( - call, serialize, deserialize); - return stream; - } -} diff --git a/packages/grpc-js-core/src/compression-filter.ts b/packages/grpc-js-core/src/compression-filter.ts deleted file mode 100644 index 134662dc8..000000000 --- a/packages/grpc-js-core/src/compression-filter.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {CallStream} from './call-stream'; -import {Channel} from './channel'; -import {BaseFilter, Filter, FilterFactory} from './filter'; -import {Metadata} from './metadata'; - -export class CompressionFilter extends BaseFilter implements Filter { - async sendMetadata(metadata: Promise): Promise { - const headers: Metadata = await metadata; - headers.set('grpc-encoding', 'identity'); - headers.set('grpc-accept-encoding', 'identity'); - return headers; - } - - async receiveMetadata(metadata: Promise): Promise { - const headers: Metadata = await metadata; - headers.remove('grpc-encoding'); - headers.remove('grpc-accept-encoding'); - return headers; - } -} - -export class CompressionFilterFactory implements - FilterFactory { - constructor(private readonly channel: Channel) {} - createFilter(callStream: CallStream): CompressionFilter { - return new CompressionFilter(); - } -} diff --git a/packages/grpc-js-core/src/constants.ts b/packages/grpc-js-core/src/constants.ts deleted file mode 100644 index a72f3388d..000000000 --- a/packages/grpc-js-core/src/constants.ts +++ /dev/null @@ -1,19 +0,0 @@ -export enum Status { - OK = 0, - CANCELLED, - UNKNOWN, - INVALID_ARGUMENT, - DEADLINE_EXCEEDED, - NOT_FOUND, - ALREADY_EXISTS, - PERMISSION_DENIED, - RESOURCE_EXHAUSTED, - FAILED_PRECONDITION, - ABORTED, - OUT_OF_RANGE, - UNIMPLEMENTED, - INTERNAL, - UNAVAILABLE, - DATA_LOSS, - UNAUTHENTICATED -} diff --git a/packages/grpc-js-core/src/deadline-filter.ts b/packages/grpc-js-core/src/deadline-filter.ts deleted file mode 100644 index 66b181076..000000000 --- a/packages/grpc-js-core/src/deadline-filter.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {CallStream} from './call-stream'; -import {Channel, Http2Channel} from './channel'; -import {Status} from './constants'; -import {BaseFilter, Filter, FilterFactory} from './filter'; -import {Metadata} from './metadata'; - -const units: [string, number][] = - [['m', 1], ['S', 1000], ['M', 60 * 1000], ['H', 60 * 60 * 1000]]; - -export class DeadlineFilter extends BaseFilter implements Filter { - private deadline: number; - constructor( - private readonly channel: Http2Channel, - private readonly callStream: CallStream) { - super(); - let callDeadline = callStream.getDeadline(); - if (callDeadline instanceof Date) { - this.deadline = callDeadline.getTime(); - } else { - this.deadline = callDeadline; - } - let now: number = (new Date()).getTime(); - let timeout = this.deadline - now; - if (timeout < 0) { - timeout = 0; - } - if (this.deadline !== Infinity) { - setTimeout(() => { - callStream.cancelWithStatus( - Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); - }, timeout); - } - } - - async sendMetadata(metadata: Promise) { - if (this.deadline === Infinity) { - return await metadata; - } - let timeoutString: Promise = - new Promise((resolve, reject) => { - this.channel.connect(() => { - let now = (new Date()).getTime(); - let timeoutMs = this.deadline - now; - for (let [unit, factor] of units) { - let amount = timeoutMs / factor; - if (amount < 1e8) { - resolve(String(Math.ceil(amount)) + unit); - return; - } - } - }); - }); - let finalMetadata = await metadata; - finalMetadata.set('grpc-timeout', await timeoutString); - return finalMetadata; - } -} - -export class DeadlineFilterFactory implements FilterFactory { - constructor(private readonly channel: Http2Channel) {} - - createFilter(callStream: CallStream): DeadlineFilter { - return new DeadlineFilter(this.channel, callStream); - } -} diff --git a/packages/grpc-js-core/src/filter-stack.ts b/packages/grpc-js-core/src/filter-stack.ts deleted file mode 100644 index 7a661692c..000000000 --- a/packages/grpc-js-core/src/filter-stack.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {flow, flowRight, map} from 'lodash'; - -import {CallStream, StatusObject} from './call-stream'; -import {Filter, FilterFactory} from './filter'; -import {Metadata} from './metadata'; - -export class FilterStack implements Filter { - constructor(private readonly filters: Filter[]) {} - - sendMetadata(metadata: Promise) { - return flow(map( - this.filters, (filter) => filter.sendMetadata.bind(filter)))(metadata); - } - - receiveMetadata(metadata: Promise) { - return flowRight( - map(this.filters, (filter) => filter.receiveMetadata.bind(filter)))( - metadata); - } - - receiveTrailers(status: Promise): Promise { - return flowRight(map( - this.filters, (filter) => filter.receiveTrailers.bind(filter)))(status); - } -} - -export class FilterStackFactory implements FilterFactory { - constructor(private readonly factories: FilterFactory[]) {} - - createFilter(callStream: CallStream): FilterStack { - return new FilterStack( - map(this.factories, (factory) => factory.createFilter(callStream))); - } -} diff --git a/packages/grpc-js-core/src/filter.ts b/packages/grpc-js-core/src/filter.ts deleted file mode 100644 index bb4e0a930..000000000 --- a/packages/grpc-js-core/src/filter.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {CallStream, StatusObject} from './call-stream'; -import {Metadata} from './metadata'; - -/** - * Filter classes represent related per-call logic and state that is primarily - * used to modify incoming and outgoing data - */ -export interface Filter { - sendMetadata(metadata: Promise): Promise; - - receiveMetadata(metadata: Promise): Promise; - - receiveTrailers(status: Promise): Promise; -} - -export abstract class BaseFilter { - async sendMetadata(metadata: Promise): Promise { - return await metadata; - } - - async receiveMetadata(metadata: Promise): Promise { - return await metadata; - } - - async receiveTrailers(status: Promise): Promise { - return await status; - } -} - -export interface FilterFactory { - createFilter(callStream: CallStream): T; -} diff --git a/packages/grpc-js-core/src/index.ts b/packages/grpc-js-core/src/index.ts deleted file mode 100644 index be03f1bb1..000000000 --- a/packages/grpc-js-core/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './call-credentials'; -export * from './channel-credentials'; -export * from './client'; -export * from './constants'; -export * from './metadata'; diff --git a/packages/grpc-js-core/src/metadata.ts b/packages/grpc-js-core/src/metadata.ts deleted file mode 100644 index c4bb2568e..000000000 --- a/packages/grpc-js-core/src/metadata.ts +++ /dev/null @@ -1,212 +0,0 @@ -import * as http2 from 'http2'; -import {forOwn} from 'lodash'; - -export type MetadataValue = string | Buffer; - -export interface MetadataObject { [key: string]: Array; } - -function cloneMetadataObject(repr: MetadataObject): MetadataObject { - const result: MetadataObject = {}; - forOwn(repr, (value, key) => { - // v.slice copies individual buffer values in value. - // TODO(kjin): Is this necessary - result[key] = value.map(v => { - if (v instanceof Buffer) { - return v.slice(); - } else { - return v; - } - }); - }); - return result; -} - -function isLegalKey(key: string): boolean { - return !!key.match(/^[0-9a-z_.-]+$/); -} - -function isLegalNonBinaryValue(value: string): boolean { - return !!value.match(/^[ -~]+$/); -} - -function isBinaryKey(key: string): boolean { - return key.endsWith('-bin'); -} - -function normalizeKey(key: string): string { - return key.toLowerCase(); -} - -function validate(key: string, value?: MetadataValue): void { - if (!isLegalKey(key)) { - throw new Error('Metadata key"' + key + '" contains illegal characters'); - } - if (value != null) { - if (isBinaryKey(key)) { - if (!(value instanceof Buffer)) { - throw new Error('keys that end with \'-bin\' must have Buffer values'); - } - } else { - if (value instanceof Buffer) { - throw new Error( - 'keys that don\'t end with \'-bin\' must have String values'); - } - if (!isLegalNonBinaryValue(value)) { - throw new Error( - 'Metadata string value "' + value + - '" contains illegal characters'); - } - } - } -} - -/** - * A class for storing metadata. Keys are normalized to lowercase ASCII. - */ -export class Metadata { - protected internalRepr: MetadataObject = {}; - - /** - * Sets the given value for the given key by replacing any other values - * associated with that key. Normalizes the key. - * @param key The key to whose value should be set. - * @param value The value to set. Must be a buffer if and only - * if the normalized key ends with '-bin'. - */ - set(key: string, value: MetadataValue): void { - key = normalizeKey(key); - validate(key, value); - this.internalRepr[key] = [value]; - } - - /** - * Adds the given value for the given key by appending to a list of previous - * values associated with that key. Normalizes the key. - * @param key The key for which a new value should be appended. - * @param value The value to add. Must be a buffer if and only - * if the normalized key ends with '-bin'. - */ - add(key: string, value: MetadataValue): void { - key = normalizeKey(key); - validate(key, value); - if (!this.internalRepr[key]) { - this.internalRepr[key] = [value]; - } else { - this.internalRepr[key].push(value); - } - } - - /** - * Removes the given key and any associated values. Normalizes the key. - * @param key The key whose values should be removed. - */ - remove(key: string): void { - key = normalizeKey(key); - validate(key); - if (Object.prototype.hasOwnProperty.call(this.internalRepr, key)) { - delete this.internalRepr[key]; - } - } - - /** - * Gets a list of all values associated with the key. Normalizes the key. - * @param key The key whose value should be retrieved. - * @return A list of values associated with the given key. - */ - get(key: string): Array { - key = normalizeKey(key); - validate(key); - if (Object.prototype.hasOwnProperty.call(this.internalRepr, key)) { - return this.internalRepr[key]; - } else { - return []; - } - } - - /** - * Gets a plain object mapping each key to the first value associated with it. - * This reflects the most common way that people will want to see metadata. - * @return A key/value mapping of the metadata. - */ - getMap(): {[key: string]: MetadataValue} { - const result: {[key: string]: MetadataValue} = {}; - forOwn(this.internalRepr, (values, key) => { - if (values.length > 0) { - const v = values[0]; - result[key] = v instanceof Buffer ? v.slice() : v; - } - }); - return result; - } - - /** - * Clones the metadata object. - * @return The newly cloned object. - */ - clone(): Metadata { - let newMetadata = new Metadata(); - newMetadata.internalRepr = cloneMetadataObject(this.internalRepr); - return newMetadata; - } - - /** - * Merges all key-value pairs from a given Metadata object into this one. - * If both this object and the given object have values in the same key, - * values from the other Metadata object will be appended to this object's - * values. - * @param other A Metadata object. - */ - merge(other: Metadata): void { - forOwn(other.internalRepr, (values, key) => { - this.internalRepr[key] = (this.internalRepr[key] || []).concat(values); - }); - } - - /** - * Creates an OutgoingHttpHeaders object that can be used with the http2 API. - */ - toHttp2Headers(): http2.OutgoingHttpHeaders { - const result: http2.OutgoingHttpHeaders = {}; - forOwn(this.internalRepr, (values, key) => { - // We assume that the user's interaction with this object is limited to - // through its public API (i.e. keys and values are already validated). - result[key] = values.map((value) => { - if (value instanceof Buffer) { - return value.toString('base64'); - } else { - return value; - } - }); - }); - return result; - } - - /** - * Returns a new Metadata object based fields in a given IncomingHttpHeaders - * object. - * @param headers An IncomingHttpHeaders object. - */ - static fromHttp2Headers(headers: http2.IncomingHttpHeaders): Metadata { - const result = new Metadata(); - forOwn(headers, (values, key) => { - if (isBinaryKey(key)) { - if (Array.isArray(values)) { - values.forEach((value) => { - result.add(key, Buffer.from(value, 'base64')); - }); - } else { - result.add(key, Buffer.from(values, 'base64')); - } - } else { - if (Array.isArray(values)) { - values.forEach((value) => { - result.add(key, value); - }); - } else { - result.add(key, values); - } - } - }); - return result; - } -} diff --git a/packages/grpc-js-core/src/object-stream.ts b/packages/grpc-js-core/src/object-stream.ts deleted file mode 100644 index bed77f042..000000000 --- a/packages/grpc-js-core/src/object-stream.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {Duplex, Readable, Writable} from 'stream'; - -export interface IntermediateObjectReadable extends Readable { - read(size?: number): any&T; -} - -export interface ObjectReadable extends IntermediateObjectReadable { - read(size?: number): T; - - addListener(event: string, listener: Function): this; - emit(event: string|symbol, ...args: any[]): boolean; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; - - addListener(event: 'data', listener: (chunk: T) => void): this; - emit(event: 'data', chunk: T): boolean; - on(event: 'data', listener: (chunk: T) => void): this; - once(event: 'data', listener: (chunk: T) => void): this; - prependListener(event: 'data', listener: (chunk: T) => void): this; - prependOnceListener(event: 'data', listener: (chunk: T) => void): this; - removeListener(event: 'data', listener: (chunk: T) => void): this; -} - -export interface IntermediateObjectWritable extends Writable { - _write(chunk: any&T, encoding: string, callback: Function): void; - write(chunk: any&T, cb?: Function): boolean; - write(chunk: any&T, encoding?: any, cb?: Function): boolean; - setDefaultEncoding(encoding: string): this; - end(): void; - end(chunk: any&T, cb?: Function): void; - end(chunk: any&T, encoding?: any, cb?: Function): void; -} - -export interface ObjectWritable extends IntermediateObjectWritable { - _write(chunk: T, encoding: string, callback: Function): void; - write(chunk: T, cb?: Function): boolean; - write(chunk: T, encoding?: any, cb?: Function): boolean; - setDefaultEncoding(encoding: string): this; - end(): void; - end(chunk: T, cb?: Function): void; - end(chunk: T, encoding?: any, cb?: Function): void; -} - -export interface ObjectDuplex extends Duplex, ObjectWritable, - ObjectReadable { - read(size?: number): U; - - _write(chunk: T, encoding: string, callback: Function): void; - write(chunk: T, cb?: Function): boolean; - write(chunk: T, encoding?: any, cb?: Function): boolean; - end(): void; - end(chunk: T, cb?: Function): void; - end(chunk: T, encoding?: any, cb?: Function): void; - - - addListener(event: string, listener: Function): this; - emit(event: string|symbol, ...args: any[]): boolean; - on(event: string, listener: Function): this; - once(event: string, listener: Function): this; - prependListener(event: string, listener: Function): this; - prependOnceListener(event: string, listener: Function): this; - removeListener(event: string, listener: Function): this; -} diff --git a/packages/grpc-js-core/test/test-call-credentials.ts b/packages/grpc-js-core/test/test-call-credentials.ts deleted file mode 100644 index ab516ed9b..000000000 --- a/packages/grpc-js-core/test/test-call-credentials.ts +++ /dev/null @@ -1,132 +0,0 @@ -import * as assert from 'assert'; - -import {CallCredentials, CallMetadataGenerator} from '../src/call-credentials'; -import {Metadata} from '../src/metadata'; - -// Metadata generators - -function makeGenerator(props: Array): CallMetadataGenerator { - return (options: {[propName: string]: string}, cb) => { - const metadata: Metadata = new Metadata(); - props.forEach((prop) => { - if (options[prop]) { - metadata.add(prop, options[prop]); - } - }); - cb(null, metadata); - }; -} - -function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator { - return (options, cb) => { - const metadata = new Metadata(); - metadata.add('msElapsed', `${ms}`); - setTimeout(() => cb(null, metadata), ms); - }; -} - -const generateFromName: CallMetadataGenerator = makeGenerator(['name']); -const generateWithError: CallMetadataGenerator = (options, cb) => - cb(new Error()); - -// Tests - -describe('CallCredentials', () => { - describe('createFromMetadataGenerator', () => { - it('should accept a metadata generator', () => { - assert.doesNotThrow( - () => CallCredentials.createFromMetadataGenerator(generateFromName)); - }); - }); - - describe('compose', () => { - it('should accept a CallCredentials object and return a new object', () => { - const callCredentials1 = - CallCredentials.createFromMetadataGenerator(generateFromName); - const callCredentials2 = - CallCredentials.createFromMetadataGenerator(generateFromName); - const combinedCredentials = callCredentials1.compose(callCredentials2); - assert.notEqual(combinedCredentials, callCredentials1); - assert.notEqual(combinedCredentials, callCredentials2); - }); - - it('should be chainable', () => { - const callCredentials1 = - CallCredentials.createFromMetadataGenerator(generateFromName); - const callCredentials2 = - CallCredentials.createFromMetadataGenerator(generateFromName); - assert.doesNotThrow(() => { - callCredentials1.compose(callCredentials2) - .compose(callCredentials2) - .compose(callCredentials2); - }); - }); - }); - - describe('generateMetadata', () => { - it('should call the function passed to createFromMetadataGenerator', - async () => { - const callCredentials = - CallCredentials.createFromMetadataGenerator(generateFromName); - let metadata: Metadata; - try { - metadata = await callCredentials.generateMetadata({name: 'foo'}); - } catch (err) { - throw err; - } - assert.deepEqual(metadata.get('name'), ['foo']); - }); - - it('should emit an error if the associated metadataGenerator does', - async () => { - const callCredentials = - CallCredentials.createFromMetadataGenerator(generateWithError); - let metadata: Metadata|null = null; - try { - metadata = await callCredentials.generateMetadata({}); - } catch (err) { - assert.ok(err instanceof Error); - } - assert.strictEqual(metadata, null); - }); - - it('should combine metadata from multiple generators', async () => { - const [callCreds1, callCreds2, callCreds3, callCreds4] = - [50, 100, 150, 200].map((ms) => { - const generator: CallMetadataGenerator = - makeAfterMsElapsedGenerator(ms); - return CallCredentials.createFromMetadataGenerator(generator); - }); - const testCases = [ - { - credentials: callCreds1.compose(callCreds2) - .compose(callCreds3) - .compose(callCreds4), - expected: ['50', '100', '150', '200'] - }, - { - credentials: callCreds4.compose( - callCreds3.compose(callCreds2.compose(callCreds1))), - expected: ['200', '150', '100', '50'] - }, - { - credentials: callCreds3.compose( - callCreds4.compose(callCreds1).compose(callCreds2)), - expected: ['150', '200', '50', '100'] - } - ]; - const options = {}; - // Try each test case and make sure the msElapsed field is as expected - await Promise.all(testCases.map(async (testCase) => { - const {credentials, expected} = testCase; - let metadata: Metadata; - try { - metadata = await credentials.generateMetadata(options); - } catch (err) { - throw err; - } - assert.deepEqual(metadata.get('msElapsed'), expected); - })); - }); - }); -}); diff --git a/packages/grpc-js-core/test/test-call-stream.ts b/packages/grpc-js-core/test/test-call-stream.ts deleted file mode 100644 index e7126d626..000000000 --- a/packages/grpc-js-core/test/test-call-stream.ts +++ /dev/null @@ -1,298 +0,0 @@ -import * as assert from 'assert'; -import { CallCredentials } from '../src/call-credentials'; -import { Http2CallStream } from '../src/call-stream'; -import { mockFunction, assert2 } from './common'; -import { Status } from '../src/constants'; -import { EventEmitter } from 'events'; -import { FilterStackFactory } from '../src/filter-stack'; -import * as http2 from 'http2'; -import { forOwn, range } from 'lodash'; -import { Metadata } from '../src/metadata'; -import * as stream from 'stream'; - -interface DataFrames { - payload: Buffer, - frameLengths: number[] -} - -const { - HTTP2_HEADER_STATUS -} = http2.constants; - -function serialize(data: string): Buffer { - const header: Buffer = Buffer.alloc(5); - header.writeUInt8(0, 0); // TODO: Uncompressed only - header.writeInt32BE(data.length, 1); - return Buffer.concat([header, Buffer.from(data, 'utf8')]); -} - -class ClientHttp2StreamMock extends stream.Duplex implements http2.ClientHttp2Stream { - constructor(private readonly dataFrames: DataFrames) { - super(); - } - emitResponse(responseCode: number, metadata?: Metadata) { - this.emit('response', { - [HTTP2_HEADER_STATUS]: responseCode, - ...metadata ? metadata.toHttp2Headers() : {} - }); - } - bytesRead = 0; - dataFrame = 0; - aborted: boolean; - destroyed: boolean; - rstCode: number; - session: http2.Http2Session; - state: http2.StreamState; - priority = mockFunction; - rstStream = mockFunction; - rstWithNoError = mockFunction; - rstWithProtocolError = mockFunction; - rstWithCancel = mockFunction; - rstWithRefuse = mockFunction; - rstWithInternalError = mockFunction; - setTimeout = mockFunction; - _read() { - if (this.dataFrame === this.dataFrames.frameLengths.length) { - if (this.bytesRead < this.dataFrames.payload.length) { - this.push(this.dataFrames.payload.slice( - this.bytesRead, this.dataFrames.payload.length)); - } - this.push(null); - return; - } - const from = this.bytesRead; - this.bytesRead += this.dataFrames.frameLengths[this.dataFrame++]; - this.push(this.dataFrames.payload.slice(from, this.bytesRead)); - }; - _write(chunk: Buffer, encoding: string, cb: Function) { - this.emit('write', chunk); - cb(); - } -} - -describe('CallStream', () => { - const callStreamArgs = { - deadline: Infinity, - credentials: CallCredentials.createEmpty(), - flags: 0 - }; - const filterStackFactory = new FilterStackFactory([]); - const message = 'eat this message'; // 16 bytes - - beforeEach(() => { - assert2.clearMustCalls(); - }); - - it('should emit a metadata event when it receives a response event', (done) => { - const responseMetadata = new Metadata(); - responseMetadata.add('key', 'value'); - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - - const http2Stream = new ClientHttp2StreamMock({ - payload: Buffer.alloc(0), - frameLengths: [] - }); - callStream.once('metadata', assert2.mustCall((metadata) => { - assert.deepStrictEqual(metadata.get('key'), ['value']); - })); - callStream.attachHttp2Stream(http2Stream); - http2Stream.emitResponse(200, responseMetadata); - assert2.afterMustCallsSatisfied(done); - }); - - it('should end a call with an error if a stream was closed', (done) => { - const c = http2.constants; - const s = Status; - const errorCodeMapping = { - [c.NGHTTP2_NO_ERROR]: s.INTERNAL, - [c.NGHTTP2_PROTOCOL_ERROR]: s.INTERNAL, - [c.NGHTTP2_INTERNAL_ERROR]: s.INTERNAL, - [c.NGHTTP2_FLOW_CONTROL_ERROR]: s.INTERNAL, - [c.NGHTTP2_SETTINGS_TIMEOUT]: s.INTERNAL, - [c.NGHTTP2_STREAM_CLOSED]: null, - [c.NGHTTP2_FRAME_SIZE_ERROR]: s.INTERNAL, - [c.NGHTTP2_REFUSED_STREAM]: s.UNAVAILABLE, - [c.NGHTTP2_CANCEL]: s.CANCELLED, - [c.NGHTTP2_COMPRESSION_ERROR]: s.INTERNAL, - [c.NGHTTP2_CONNECT_ERROR]: s.INTERNAL, - [c.NGHTTP2_ENHANCE_YOUR_CALM]: s.RESOURCE_EXHAUSTED, - [c.NGHTTP2_INADEQUATE_SECURITY]: s.PERMISSION_DENIED - }; - forOwn(errorCodeMapping, (value: Status | null, key) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: Buffer.alloc(0), - frameLengths: [] - }); - callStream.attachHttp2Stream(http2Stream); - if (value !== null) { - callStream.once('status', assert2.mustCall((status) => { - assert.strictEqual(status.code, value); - })); - } - http2Stream.emit('streamClosed', Number(key)); - }); - assert2.afterMustCallsSatisfied(done); - }); - - it('should have functioning getters', (done) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - assert.strictEqual(callStream.getDeadline(), callStreamArgs.deadline); - assert.strictEqual(callStream.getCredentials(), callStreamArgs.credentials); - assert.strictEqual(callStream.getStatus(), null); - callStream.on('status', assert2.mustCall((status) => { - assert.strictEqual(status.code, Status.CANCELLED); - assert.strictEqual(status.details, ';)'); - assert.strictEqual(callStream.getStatus(), status); - })); - callStream.cancelWithStatus(Status.CANCELLED, ';)'); - // TODO: getPeer - assert2.afterMustCallsSatisfied(done); - }); - - describe('attachHttp2Stream', () => { - it('should handle an empty message', (done) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: serialize(''), - frameLengths: [] - }); - callStream.once('data', assert2.mustCall((buffer) => { - assert.strictEqual(buffer.toString('utf8'), ''); - })); - callStream.attachHttp2Stream(http2Stream); - assert2.afterMustCallsSatisfied(done); - }); - - [ - { - description: 'all data is supplied in a single frame', - frameLengths: [] - }, - { - description: 'frames are split along header field delimiters', - frameLengths: [1, 4] - }, - { - description: 'portions of header fields are split between different frames', - frameLengths: [2, 1, 1, 4] - }, - { - description: 'frames are split into bytes', - frameLengths: range(0, 20).map(() => 1) - } - ].forEach((testCase: { description: string, frameLengths: number[] }) => { - it(`should handle a short message where ${testCase.description}`, (done) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: serialize(message), // 21 bytes - frameLengths: testCase.frameLengths - }); - callStream.once('data', assert2.mustCall((buffer) => { - assert.strictEqual(buffer.toString('utf8'), message); - })); - callStream.once('end', assert2.mustCall(() => {})); - callStream.attachHttp2Stream(http2Stream); - assert2.afterMustCallsSatisfied(done); - }); - }); - - [ - { - description: 'all data is supplied in a single frame', - frameLengths: [] - }, - { - description: 'frames are split between delimited messages', - frameLengths: [21] - }, - { - description: 'frames are split within messages', - frameLengths: [10, 22] - }, - { - description: 'part of 2nd message\'s header is in first frame', - frameLengths: [24] - }, - { - description: 'frames are split into bytes', - frameLengths: range(0, 41).map(() => 1) - } - ].forEach((testCase: { description: string, frameLengths: number[] }) => { - it(`should handle two messages where ${testCase.description}`, (done) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: Buffer.concat([serialize(message), serialize(message)]), // 42 bytes - frameLengths: testCase.frameLengths - }); - callStream.once('data', assert2.mustCall((buffer) => { - assert.strictEqual(buffer.toString('utf8'), message); - })); - callStream.once('data', assert2.mustCall((buffer) => { - assert.strictEqual(buffer.toString('utf8'), message); - })); - callStream.once('end', assert2.mustCall(() => {})); - callStream.attachHttp2Stream(http2Stream); - assert2.afterMustCallsSatisfied(done); - }); - }); - - it('should send buffered writes', (done) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: Buffer.alloc(0), - frameLengths: [] - }); - let streamFlushed = false; - http2Stream.once('write', assert2.mustCall((chunk: Buffer) => { - const dataLength = chunk.readInt32BE(1); - const encodedMessage = chunk.slice(5).toString('utf8'); - assert.strictEqual(dataLength, message.length); - assert.strictEqual(encodedMessage, message); - streamFlushed = true; - })); - callStream.write({ - message: Buffer.from(message) - }, assert2.mustCall(() => { - // Ensure this is called only after contents are written to http2Stream - assert.ok(streamFlushed); - })); - callStream.end(assert2.mustCall(() => {})); - callStream.attachHttp2Stream(http2Stream); - assert2.afterMustCallsSatisfied(done); - }); - - it('should cause data chunks in write calls afterward to be written to the given stream', (done) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: Buffer.alloc(0), - frameLengths: [] - }); - http2Stream.once('write', assert2.mustCall((chunk: Buffer) => { - const dataLength = chunk.readInt32BE(1); - const encodedMessage = chunk.slice(5).toString('utf8'); - assert.strictEqual(dataLength, message.length); - assert.strictEqual(encodedMessage, message); - })); - callStream.attachHttp2Stream(http2Stream); - callStream.write({ - message: Buffer.from(message) - }, assert2.mustCall(() => {})); - callStream.end(assert2.mustCall(() => {})); - assert2.afterMustCallsSatisfied(done); - }); - - it('should handle underlying stream errors', () => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: Buffer.alloc(0), - frameLengths: [] - }); - callStream.once('status', assert2.mustCall((status) => { - assert.strictEqual(status.code, Status.INTERNAL); - })); - callStream.attachHttp2Stream(http2Stream); - http2Stream.emit('error'); - }); - }); -}); diff --git a/packages/grpc-js-core/test/test-channel-credentials.ts b/packages/grpc-js-core/test/test-channel-credentials.ts deleted file mode 100644 index fac881107..000000000 --- a/packages/grpc-js-core/test/test-channel-credentials.ts +++ /dev/null @@ -1,115 +0,0 @@ -import * as assert from 'assert'; -import * as fs from 'fs'; -import {promisify} from 'util'; - -import {CallCredentials} from '../src/call-credentials'; -import {ChannelCredentials} from '../src/channel-credentials'; - -import {assert2, mockFunction} from './common'; - -class CallCredentialsMock implements CallCredentials { - child: CallCredentialsMock; - constructor(child?: CallCredentialsMock) { - if (child) { - this.child = child; - } - } - - generateMetadata = mockFunction; - - compose(callCredentials: CallCredentialsMock): CallCredentialsMock { - return new CallCredentialsMock(callCredentials); - } - - isEqual(other: CallCredentialsMock): boolean { - if (!this.child) { - return this === other; - } else if (!other || !other.child) { - return false; - } else { - return this.child.isEqual(other.child); - } - } -} - -const readFile: (...args: any[]) => Promise = promisify(fs.readFile); -// A promise which resolves to loaded files in the form { ca, key, cert } -const pFixtures = Promise - .all(['ca.pem', 'server1.key', 'server1.pem'].map( - (file) => readFile(`${__dirname}/fixtures/${file}`))) - .then((result) => { - return {ca: result[0], key: result[1], cert: result[2]}; - }); - -describe('ChannelCredentials Implementation', () => { - describe('createInsecure', () => { - it('should return a ChannelCredentials object with no associated secure context', - () => { - const creds = assert2.noThrowAndReturn( - () => ChannelCredentials.createInsecure()); - assert.ok(!creds.getSecureContext()); - }); - }); - - describe('createSsl', () => { - it('should work when given no arguments', () => { - const creds: ChannelCredentials = - assert2.noThrowAndReturn(() => ChannelCredentials.createSsl()); - assert.ok(!!creds.getSecureContext()); - }); - - it('should work with just a CA override', async () => { - const {ca} = await pFixtures; - const creds = - assert2.noThrowAndReturn(() => ChannelCredentials.createSsl(ca)); - assert.ok(!!creds.getSecureContext()); - }); - - it('should work with just a private key and cert chain', async () => { - const {key, cert} = await pFixtures; - const creds = assert2.noThrowAndReturn( - () => ChannelCredentials.createSsl(null, key, cert)); - assert.ok(!!creds.getSecureContext()); - }); - - it('should work with all three parameters specified', async () => { - const {ca, key, cert} = await pFixtures; - const creds = assert2.noThrowAndReturn( - () => ChannelCredentials.createSsl(ca, key, cert)); - assert.ok(!!creds.getSecureContext()); - }); - - it('should throw if just one of private key and cert chain are missing', - async () => { - const {ca, key, cert} = await pFixtures; - assert.throws(() => ChannelCredentials.createSsl(ca, key)); - assert.throws(() => ChannelCredentials.createSsl(ca, key, null)); - assert.throws(() => ChannelCredentials.createSsl(ca, null, cert)); - assert.throws(() => ChannelCredentials.createSsl(null, key)); - assert.throws(() => ChannelCredentials.createSsl(null, key, null)); - assert.throws(() => ChannelCredentials.createSsl(null, null, cert)); - }); - }); - - describe('compose', () => { - it('should return a ChannelCredentials object', () => { - const channelCreds = ChannelCredentials.createSsl(); - const callCreds = new CallCredentialsMock(); - const composedChannelCreds = channelCreds.compose(callCreds); - assert.strictEqual(composedChannelCreds.getCallCredentials(), callCreds); - }); - - it('should be chainable', () => { - const callCreds1 = new CallCredentialsMock(); - const callCreds2 = new CallCredentialsMock(); - // Associate both call credentials with channelCreds - const composedChannelCreds = ChannelCredentials.createSsl() - .compose(callCreds1) - .compose(callCreds2); - // Build a mock object that should be an identical copy - const composedCallCreds = callCreds1.compose(callCreds2); - assert.ok(composedCallCreds.isEqual( - composedChannelCreds.getCallCredentials() as CallCredentialsMock)); - }); - }); -}); diff --git a/packages/grpc-js-core/tsconfig.json b/packages/grpc-js-core/tsconfig.json deleted file mode 100644 index 62024719e..000000000 --- a/packages/grpc-js-core/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "./node_modules/google-ts-style/tsconfig-google.json", - "compilerOptions": { - "lib": [ "es6" ], - "typeRoots": [ - "node_modules/h2-types", "node_modules/@types" - ] - }, - "include": [ - "src/**/*.ts", - "test/**/*.ts" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/packages/grpc-js-xds/.eslintrc.json b/packages/grpc-js-xds/.eslintrc.json new file mode 100644 index 000000000..f95bb333f --- /dev/null +++ b/packages/grpc-js-xds/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/gts/" +} diff --git a/packages/grpc-js-xds/.prettierrc.js b/packages/grpc-js-xds/.prettierrc.js new file mode 100644 index 000000000..c634ea729 --- /dev/null +++ b/packages/grpc-js-xds/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('gts/.prettierrc.json') +} \ No newline at end of file diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md new file mode 100644 index 000000000..793e0c0d7 --- /dev/null +++ b/packages/grpc-js-xds/README.md @@ -0,0 +1,32 @@ +# @grpc/grpc-js xDS plugin + +This package provides support for the `xds://` URL scheme to the `@grpc/grpc-js` library. The latest version of this package is compatible with `@grpc/grpc-js` version 1.2.x. + +## Installation + +``` +npm install @grpc/grpc-js-xds +``` + +## Usage + +```ts +import * as grpcJsXds from '@grpc/grpc-js-xds'; +grpcJsXds.register(); + +// ...get a @grpc/grpc-js Client class as usual + +const client = new MyServiceClient('xds:///example.com:123'); +``` + +## Supported Features + + - [xDS-Based Global Load Balancing](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md) + - [xDS traffic splitting and routing](https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md) + - [xDS v3 API](https://github.com/grpc/proposal/blob/master/A30-xds-v3.md) + - [xDS Timeouts](https://github.com/grpc/proposal/blob/master/A31-xds-timeout-support-and-config-selector.md) + - [xDS Circuit Breaking](https://github.com/grpc/proposal/blob/master/A32-xds-circuit-breaking.md) + - [xDS Client-Side Fault Injection](https://github.com/grpc/proposal/blob/master/A33-Fault-Injection.md) + - [Client Status Discovery Service](https://github.com/grpc/proposal/blob/master/A40-csds-support.md) + - [Outlier Detection](https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md) + - [xDS Retry Support](https://github.com/grpc/proposal/blob/master/A44-xds-retry.md) \ No newline at end of file diff --git a/packages/grpc-js-xds/deps/envoy-api b/packages/grpc-js-xds/deps/envoy-api new file mode 160000 index 000000000..20b1b5fce --- /dev/null +++ b/packages/grpc-js-xds/deps/envoy-api @@ -0,0 +1 @@ +Subproject commit 20b1b5fcee88a20a08b71051a961181839ec7268 diff --git a/packages/grpc-js-xds/deps/googleapis b/packages/grpc-js-xds/deps/googleapis new file mode 160000 index 000000000..8c53b2cb7 --- /dev/null +++ b/packages/grpc-js-xds/deps/googleapis @@ -0,0 +1 @@ +Subproject commit 8c53b2cb792234354c13336ac7daee61333deade diff --git a/packages/grpc-js-xds/deps/protoc-gen-validate b/packages/grpc-js-xds/deps/protoc-gen-validate new file mode 160000 index 000000000..0af61d9dc --- /dev/null +++ b/packages/grpc-js-xds/deps/protoc-gen-validate @@ -0,0 +1 @@ +Subproject commit 0af61d9dc28712dc0e6f8e1a940855a2ee0cb9ed diff --git a/packages/grpc-js-xds/deps/xds b/packages/grpc-js-xds/deps/xds new file mode 160000 index 000000000..cb28da345 --- /dev/null +++ b/packages/grpc-js-xds/deps/xds @@ -0,0 +1 @@ +Subproject commit cb28da3451f158a947dfc45090fe92b07b243bc1 diff --git a/packages/grpc-js-xds/gulpfile.ts b/packages/grpc-js-xds/gulpfile.ts new file mode 100644 index 000000000..4ee6ac2c5 --- /dev/null +++ b/packages/grpc-js-xds/gulpfile.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2020 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. + * + */ + +import * as gulp from 'gulp'; + +import * as mocha from 'gulp-mocha'; +import * as path from 'path'; +import * as execa from 'execa'; +import * as semver from 'semver'; + +Error.stackTraceLimit = Infinity; + +const jsCoreDir = __dirname; +const outDir = path.resolve(jsCoreDir, 'build'); + +const pkgPath = path.resolve(jsCoreDir, 'package.json'); +const supportedVersionRange = require(pkgPath).engines.node; +const versionNotSupported = () => { + console.log(`Skipping grpc-js-xds task for Node ${process.version}`); + return () => { return Promise.resolve(); }; +}; +const identity = (value: any): any => value; +const checkTask = semver.satisfies(process.version, supportedVersionRange) ? + identity : versionNotSupported; + +const execNpmVerb = (verb: string, ...args: string[]) => + execa('npm', [verb, ...args], {cwd: jsCoreDir, stdio: 'inherit'}); +const execNpmCommand = execNpmVerb.bind(null, 'run'); + +const install = checkTask(() => execNpmVerb('install', '--unsafe-perm')); + +/** + * Runs tslint on files in src/, with linting rules defined in tslint.json. + */ +const lint = checkTask(() => execNpmCommand('check')); + +const cleanFiles = checkTask(() => execNpmCommand('clean')); + +const clean = gulp.series(install, cleanFiles); + +const cleanAll = gulp.parallel(clean); + +/** + * Transpiles TypeScript files in src/ to JavaScript according to the settings + * found in tsconfig.json. + */ +const compile = checkTask(() => execNpmCommand('compile')); + +const runTests = checkTask(() => { + return gulp.src(`${outDir}/test/**/*.js`) + .pipe(mocha({reporter: 'mocha-jenkins-reporter', + require: ['ts-node/register']})); +}); + +const test = gulp.series(install, runTests); + +export { + install, + lint, + clean, + cleanAll, + compile, + test +} diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile new file mode 100644 index 000000000..5ff12a433 --- /dev/null +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -0,0 +1,38 @@ +# Copyright 2022 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. + +# Dockerfile for building the xDS interop client. To build the image, run the +# following command from grpc-node directory: +# docker build -t -f packages/grpc-js-xds/interop/Dockerfile . + +FROM node:18-slim as build + +# Make a grpc-node directory and copy the repo into it. +WORKDIR /node/src/grpc-node +COPY . . + +WORKDIR /node/src/grpc-node/packages/grpc-js +RUN npm install +WORKDIR /node/src/grpc-node/packages/grpc-js-xds +RUN npm install + +FROM gcr.io/distroless/nodejs18-debian11:latest +WORKDIR /node/src/grpc-node +COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ +COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ + +ENV GRPC_VERBOSITY="DEBUG" +ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection + +ENTRYPOINT [ "/nodejs/bin/node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/BoolValue.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/BoolValue.ts new file mode 100644 index 000000000..a1e31ab33 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/BoolValue.ts @@ -0,0 +1,26 @@ +// Original file: proto/grpc/testing/messages.proto + + +/** + * 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"; + */ +export interface BoolValue { + /** + * The bool value. + */ + 'value'?: (boolean); +} + +/** + * 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"; + */ +export interface BoolValue__Output { + /** + * The bool value. + */ + 'value': (boolean); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureRequest.ts new file mode 100644 index 000000000..7f07e8966 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureRequest.ts @@ -0,0 +1,68 @@ +// Original file: proto/grpc/testing/messages.proto + + +/** + * Metadata to be attached for the given type of RPCs. + */ +export interface _grpc_testing_ClientConfigureRequest_Metadata { + 'type'?: (_grpc_testing_ClientConfigureRequest_RpcType | keyof typeof _grpc_testing_ClientConfigureRequest_RpcType); + 'key'?: (string); + 'value'?: (string); +} + +/** + * Metadata to be attached for the given type of RPCs. + */ +export interface _grpc_testing_ClientConfigureRequest_Metadata__Output { + 'type': (keyof typeof _grpc_testing_ClientConfigureRequest_RpcType); + 'key': (string); + 'value': (string); +} + +// Original file: proto/grpc/testing/messages.proto + +/** + * Type of RPCs to send. + */ +export enum _grpc_testing_ClientConfigureRequest_RpcType { + EMPTY_CALL = 0, + UNARY_CALL = 1, +} + +/** + * Configurations for a test client. + */ +export interface ClientConfigureRequest { + /** + * The types of RPCs the client sends. + */ + 'types'?: (_grpc_testing_ClientConfigureRequest_RpcType | keyof typeof _grpc_testing_ClientConfigureRequest_RpcType)[]; + /** + * The collection of custom metadata to be attached to RPCs sent by the client. + */ + 'metadata'?: (_grpc_testing_ClientConfigureRequest_Metadata)[]; + /** + * The deadline to use, in seconds, for all RPCs. If unset or zero, the + * client will use the default from the command-line. + */ + 'timeout_sec'?: (number); +} + +/** + * Configurations for a test client. + */ +export interface ClientConfigureRequest__Output { + /** + * The types of RPCs the client sends. + */ + 'types': (keyof typeof _grpc_testing_ClientConfigureRequest_RpcType)[]; + /** + * The collection of custom metadata to be attached to RPCs sent by the client. + */ + 'metadata': (_grpc_testing_ClientConfigureRequest_Metadata__Output)[]; + /** + * The deadline to use, in seconds, for all RPCs. If unset or zero, the + * client will use the default from the command-line. + */ + 'timeout_sec': (number); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureResponse.ts new file mode 100644 index 000000000..df663240a --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureResponse.ts @@ -0,0 +1,14 @@ +// Original file: proto/grpc/testing/messages.proto + + +/** + * Response for updating a test client's configuration. + */ +export interface ClientConfigureResponse { +} + +/** + * Response for updating a test client's configuration. + */ +export interface ClientConfigureResponse__Output { +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/EchoStatus.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/EchoStatus.ts new file mode 100644 index 000000000..d5da75017 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/EchoStatus.ts @@ -0,0 +1,20 @@ +// Original file: proto/grpc/testing/messages.proto + + +/** + * A protobuf representation for grpc status. This is used by test + * clients to specify a status that the server should attempt to return. + */ +export interface EchoStatus { + 'code'?: (number); + 'message'?: (string); +} + +/** + * A protobuf representation for grpc status. This is used by test + * clients to specify a status that the server should attempt to return. + */ +export interface EchoStatus__Output { + 'code': (number); + 'message': (string); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/Empty.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/Empty.ts new file mode 100644 index 000000000..d79db52be --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/Empty.ts @@ -0,0 +1,26 @@ +// Original file: proto/grpc/testing/empty.proto + + +/** + * 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) { }; + * }; + */ +export interface Empty { +} + +/** + * 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) { }; + * }; + */ +export interface Empty__Output { +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/GrpclbRouteType.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/GrpclbRouteType.ts new file mode 100644 index 000000000..8ab0146b7 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/GrpclbRouteType.ts @@ -0,0 +1,24 @@ +// Original file: proto/grpc/testing/messages.proto + +/** + * The type of route that a client took to reach a server w.r.t. gRPCLB. + * The server must fill in "fallback" if it detects that the RPC reached + * the server via the "gRPCLB fallback" path, and "backend" if it detects + * that the RPC reached the server via "gRPCLB backend" path (i.e. if it got + * the address of this server from the gRPCLB server BalanceLoad RPC). Exactly + * how this detection is done is context and server dependent. + */ +export enum GrpclbRouteType { + /** + * Server didn't detect the route that a client took to reach it. + */ + GRPCLB_ROUTE_TYPE_UNKNOWN = 0, + /** + * Indicates that a client reached a server via gRPCLB fallback. + */ + GRPCLB_ROUTE_TYPE_FALLBACK = 1, + /** + * Indicates that a client reached a server as a gRPCLB-given backend. + */ + GRPCLB_ROUTE_TYPE_BACKEND = 2, +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsRequest.ts new file mode 100644 index 000000000..1aa9773c5 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsRequest.ts @@ -0,0 +1,14 @@ +// Original file: proto/grpc/testing/messages.proto + + +/** + * Request for retrieving a test client's accumulated stats. + */ +export interface LoadBalancerAccumulatedStatsRequest { +} + +/** + * Request for retrieving a test client's accumulated stats. + */ +export interface LoadBalancerAccumulatedStatsRequest__Output { +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsResponse.ts new file mode 100644 index 000000000..91157ac4e --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsResponse.ts @@ -0,0 +1,78 @@ +// Original file: proto/grpc/testing/messages.proto + + +export interface _grpc_testing_LoadBalancerAccumulatedStatsResponse_MethodStats { + /** + * The number of RPCs that were started for this method. + */ + 'rpcs_started'?: (number); + /** + * The number of RPCs that completed with each status for this method. The + * key is the integral value of a google.rpc.Code; the value is the count. + */ + 'result'?: ({[key: number]: number}); +} + +export interface _grpc_testing_LoadBalancerAccumulatedStatsResponse_MethodStats__Output { + /** + * The number of RPCs that were started for this method. + */ + 'rpcs_started': (number); + /** + * The number of RPCs that completed with each status for this method. The + * key is the integral value of a google.rpc.Code; the value is the count. + */ + 'result': ({[key: number]: number}); +} + +/** + * Accumulated stats for RPCs sent by a test client. + */ +export interface LoadBalancerAccumulatedStatsResponse { + /** + * The total number of RPCs have ever issued for each type. + * Deprecated: use stats_per_method.rpcs_started instead. + */ + 'num_rpcs_started_by_method'?: ({[key: string]: number}); + /** + * The total number of RPCs have ever completed successfully for each type. + * Deprecated: use stats_per_method.result instead. + */ + 'num_rpcs_succeeded_by_method'?: ({[key: string]: number}); + /** + * The total number of RPCs have ever failed for each type. + * Deprecated: use stats_per_method.result instead. + */ + 'num_rpcs_failed_by_method'?: ({[key: string]: number}); + /** + * Per-method RPC statistics. The key is the RpcType in string form; e.g. + * 'EMPTY_CALL' or 'UNARY_CALL' + */ + 'stats_per_method'?: ({[key: string]: _grpc_testing_LoadBalancerAccumulatedStatsResponse_MethodStats}); +} + +/** + * Accumulated stats for RPCs sent by a test client. + */ +export interface LoadBalancerAccumulatedStatsResponse__Output { + /** + * The total number of RPCs have ever issued for each type. + * Deprecated: use stats_per_method.rpcs_started instead. + */ + 'num_rpcs_started_by_method': ({[key: string]: number}); + /** + * The total number of RPCs have ever completed successfully for each type. + * Deprecated: use stats_per_method.result instead. + */ + 'num_rpcs_succeeded_by_method': ({[key: string]: number}); + /** + * The total number of RPCs have ever failed for each type. + * Deprecated: use stats_per_method.result instead. + */ + 'num_rpcs_failed_by_method': ({[key: string]: number}); + /** + * Per-method RPC statistics. The key is the RpcType in string form; e.g. + * 'EMPTY_CALL' or 'UNARY_CALL' + */ + 'stats_per_method'?: ({[key: string]: _grpc_testing_LoadBalancerAccumulatedStatsResponse_MethodStats__Output}); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsRequest.ts new file mode 100644 index 000000000..189d871b0 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsRequest.ts @@ -0,0 +1,24 @@ +// Original file: proto/grpc/testing/messages.proto + + +export interface LoadBalancerStatsRequest { + /** + * Request stats for the next num_rpcs sent by client. + */ + 'num_rpcs'?: (number); + /** + * If num_rpcs have not completed within timeout_sec, return partial results. + */ + 'timeout_sec'?: (number); +} + +export interface LoadBalancerStatsRequest__Output { + /** + * Request stats for the next num_rpcs sent by client. + */ + 'num_rpcs': (number); + /** + * If num_rpcs have not completed within timeout_sec, return partial results. + */ + 'timeout_sec': (number); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsResponse.ts new file mode 100644 index 000000000..184a6e258 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsResponse.ts @@ -0,0 +1,40 @@ +// Original file: proto/grpc/testing/messages.proto + + +export interface _grpc_testing_LoadBalancerStatsResponse_RpcsByPeer { + /** + * The number of completed RPCs for each peer. + */ + 'rpcs_by_peer'?: ({[key: string]: number}); +} + +export interface _grpc_testing_LoadBalancerStatsResponse_RpcsByPeer__Output { + /** + * The number of completed RPCs for each peer. + */ + 'rpcs_by_peer': ({[key: string]: number}); +} + +export interface LoadBalancerStatsResponse { + /** + * The number of completed RPCs for each peer. + */ + 'rpcs_by_peer'?: ({[key: string]: number}); + /** + * The number of RPCs that failed to record a remote peer. + */ + 'num_failures'?: (number); + 'rpcs_by_method'?: ({[key: string]: _grpc_testing_LoadBalancerStatsResponse_RpcsByPeer}); +} + +export interface LoadBalancerStatsResponse__Output { + /** + * The number of completed RPCs for each peer. + */ + 'rpcs_by_peer': ({[key: string]: number}); + /** + * The number of RPCs that failed to record a remote peer. + */ + 'num_failures': (number); + 'rpcs_by_method'?: ({[key: string]: _grpc_testing_LoadBalancerStatsResponse_RpcsByPeer__Output}); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsService.ts new file mode 100644 index 000000000..26cfee9d7 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsService.ts @@ -0,0 +1,59 @@ +// Original file: proto/grpc/testing/test.proto + +import type * as grpc from '@grpc/grpc-js' +import type { LoadBalancerAccumulatedStatsRequest as _grpc_testing_LoadBalancerAccumulatedStatsRequest, LoadBalancerAccumulatedStatsRequest__Output as _grpc_testing_LoadBalancerAccumulatedStatsRequest__Output } from '../../grpc/testing/LoadBalancerAccumulatedStatsRequest'; +import type { LoadBalancerAccumulatedStatsResponse as _grpc_testing_LoadBalancerAccumulatedStatsResponse, LoadBalancerAccumulatedStatsResponse__Output as _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output } from '../../grpc/testing/LoadBalancerAccumulatedStatsResponse'; +import type { LoadBalancerStatsRequest as _grpc_testing_LoadBalancerStatsRequest, LoadBalancerStatsRequest__Output as _grpc_testing_LoadBalancerStatsRequest__Output } from '../../grpc/testing/LoadBalancerStatsRequest'; +import type { LoadBalancerStatsResponse as _grpc_testing_LoadBalancerStatsResponse, LoadBalancerStatsResponse__Output as _grpc_testing_LoadBalancerStatsResponse__Output } from '../../grpc/testing/LoadBalancerStatsResponse'; + +/** + * A service used to obtain stats for verifying LB behavior. + */ +export interface LoadBalancerStatsServiceClient extends grpc.Client { + /** + * Gets the accumulated stats for RPCs sent by a test client. + */ + GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Gets the accumulated stats for RPCs sent by a test client. + */ + getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * Gets the backend distribution for RPCs sent by a test client. + */ + GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Gets the backend distribution for RPCs sent by a test client. + */ + getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + +} + +/** + * A service used to obtain stats for verifying LB behavior. + */ +export interface LoadBalancerStatsServiceHandlers extends grpc.UntypedServiceImplementation { + /** + * Gets the accumulated stats for RPCs sent by a test client. + */ + GetClientAccumulatedStats: grpc.handleUnaryCall<_grpc_testing_LoadBalancerAccumulatedStatsRequest__Output, _grpc_testing_LoadBalancerAccumulatedStatsResponse>; + + /** + * Gets the backend distribution for RPCs sent by a test client. + */ + GetClientStats: grpc.handleUnaryCall<_grpc_testing_LoadBalancerStatsRequest__Output, _grpc_testing_LoadBalancerStatsResponse>; + +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/Payload.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/Payload.ts new file mode 100644 index 000000000..79102d2bf --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/Payload.ts @@ -0,0 +1,31 @@ +// Original file: proto/grpc/testing/messages.proto + +import type { PayloadType as _grpc_testing_PayloadType } from '../../grpc/testing/PayloadType'; + +/** + * A block of data, to simply increase gRPC message size. + */ +export interface Payload { + /** + * The type of data in body. + */ + 'type'?: (_grpc_testing_PayloadType | keyof typeof _grpc_testing_PayloadType); + /** + * Primary contents of payload. + */ + 'body'?: (Buffer | Uint8Array | string); +} + +/** + * A block of data, to simply increase gRPC message size. + */ +export interface Payload__Output { + /** + * The type of data in body. + */ + 'type': (keyof typeof _grpc_testing_PayloadType); + /** + * Primary contents of payload. + */ + 'body': (Buffer); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/PayloadType.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/PayloadType.ts new file mode 100644 index 000000000..3cf9d375a --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/PayloadType.ts @@ -0,0 +1,11 @@ +// Original file: proto/grpc/testing/messages.proto + +/** + * The type of payload that should be returned. + */ +export enum PayloadType { + /** + * Compressable text format. + */ + COMPRESSABLE = 0, +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectInfo.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectInfo.ts new file mode 100644 index 000000000..616de9ebf --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectInfo.ts @@ -0,0 +1,22 @@ +// Original file: proto/grpc/testing/messages.proto + + +/** + * For reconnect interop test only. + * Server tells client whether its reconnects are following the spec and the + * reconnect backoffs it saw. + */ +export interface ReconnectInfo { + 'passed'?: (boolean); + 'backoff_ms'?: (number)[]; +} + +/** + * For reconnect interop test only. + * Server tells client whether its reconnects are following the spec and the + * reconnect backoffs it saw. + */ +export interface ReconnectInfo__Output { + 'passed': (boolean); + 'backoff_ms': (number)[]; +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectParams.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectParams.ts new file mode 100644 index 000000000..1337b5688 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectParams.ts @@ -0,0 +1,18 @@ +// Original file: proto/grpc/testing/messages.proto + + +/** + * For reconnect interop test only. + * Client tells server what reconnection parameters it used. + */ +export interface ReconnectParams { + 'max_reconnect_backoff_ms'?: (number); +} + +/** + * For reconnect interop test only. + * Client tells server what reconnection parameters it used. + */ +export interface ReconnectParams__Output { + 'max_reconnect_backoff_ms': (number); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectService.ts new file mode 100644 index 000000000..e489e2849 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectService.ts @@ -0,0 +1,40 @@ +// Original file: proto/grpc/testing/test.proto + +import type * as grpc from '@grpc/grpc-js' +import type { Empty as _grpc_testing_Empty, Empty__Output as _grpc_testing_Empty__Output } from '../../grpc/testing/Empty'; +import type { ReconnectInfo as _grpc_testing_ReconnectInfo, ReconnectInfo__Output as _grpc_testing_ReconnectInfo__Output } from '../../grpc/testing/ReconnectInfo'; +import type { ReconnectParams as _grpc_testing_ReconnectParams, ReconnectParams__Output as _grpc_testing_ReconnectParams__Output } from '../../grpc/testing/ReconnectParams'; + +/** + * A service used to control reconnect server. + */ +export interface ReconnectServiceClient extends grpc.Client { + Start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + Start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + Start(argument: _grpc_testing_ReconnectParams, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + Start(argument: _grpc_testing_ReconnectParams, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + start(argument: _grpc_testing_ReconnectParams, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + start(argument: _grpc_testing_ReconnectParams, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + + Stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + Stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + Stop(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + Stop(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + stop(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + stop(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + +} + +/** + * A service used to control reconnect server. + */ +export interface ReconnectServiceHandlers extends grpc.UntypedServiceImplementation { + Start: grpc.handleUnaryCall<_grpc_testing_ReconnectParams__Output, _grpc_testing_Empty>; + + Stop: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_ReconnectInfo>; + +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ResponseParameters.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ResponseParameters.ts new file mode 100644 index 000000000..04ca94ced --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ResponseParameters.ts @@ -0,0 +1,47 @@ +// Original file: proto/grpc/testing/messages.proto + +import type { BoolValue as _grpc_testing_BoolValue, BoolValue__Output as _grpc_testing_BoolValue__Output } from '../../grpc/testing/BoolValue'; + +/** + * Configuration for a particular response. + */ +export interface ResponseParameters { + /** + * Desired payload sizes in responses from the server. + */ + 'size'?: (number); + /** + * Desired interval between consecutive responses in the response stream in + * microseconds. + */ + 'interval_us'?: (number); + /** + * 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. + */ + 'compressed'?: (_grpc_testing_BoolValue); +} + +/** + * Configuration for a particular response. + */ +export interface ResponseParameters__Output { + /** + * Desired payload sizes in responses from the server. + */ + 'size': (number); + /** + * Desired interval between consecutive responses in the response stream in + * microseconds. + */ + 'interval_us': (number); + /** + * 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. + */ + 'compressed'?: (_grpc_testing_BoolValue__Output); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleRequest.ts new file mode 100644 index 000000000..056eb10b2 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleRequest.ts @@ -0,0 +1,106 @@ +// Original file: proto/grpc/testing/messages.proto + +import type { PayloadType as _grpc_testing_PayloadType } from '../../grpc/testing/PayloadType'; +import type { Payload as _grpc_testing_Payload, Payload__Output as _grpc_testing_Payload__Output } from '../../grpc/testing/Payload'; +import type { BoolValue as _grpc_testing_BoolValue, BoolValue__Output as _grpc_testing_BoolValue__Output } from '../../grpc/testing/BoolValue'; +import type { EchoStatus as _grpc_testing_EchoStatus, EchoStatus__Output as _grpc_testing_EchoStatus__Output } from '../../grpc/testing/EchoStatus'; + +/** + * Unary request. + */ +export interface SimpleRequest { + /** + * Desired payload type in the response from the server. + * If response_type is RANDOM, server randomly chooses one from other formats. + */ + 'response_type'?: (_grpc_testing_PayloadType | keyof typeof _grpc_testing_PayloadType); + /** + * Desired payload size in the response from the server. + */ + 'response_size'?: (number); + /** + * Optional input payload sent along with the request. + */ + 'payload'?: (_grpc_testing_Payload); + /** + * Whether SimpleResponse should include username. + */ + 'fill_username'?: (boolean); + /** + * Whether SimpleResponse should include OAuth scope. + */ + 'fill_oauth_scope'?: (boolean); + /** + * 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. + */ + 'response_compressed'?: (_grpc_testing_BoolValue); + /** + * Whether server should return a given status + */ + 'response_status'?: (_grpc_testing_EchoStatus); + /** + * Whether the server should expect this request to be compressed. + */ + 'expect_compressed'?: (_grpc_testing_BoolValue); + /** + * Whether SimpleResponse should include server_id. + */ + 'fill_server_id'?: (boolean); + /** + * Whether SimpleResponse should include grpclb_route_type. + */ + 'fill_grpclb_route_type'?: (boolean); +} + +/** + * Unary request. + */ +export interface SimpleRequest__Output { + /** + * Desired payload type in the response from the server. + * If response_type is RANDOM, server randomly chooses one from other formats. + */ + 'response_type': (keyof typeof _grpc_testing_PayloadType); + /** + * Desired payload size in the response from the server. + */ + 'response_size': (number); + /** + * Optional input payload sent along with the request. + */ + 'payload'?: (_grpc_testing_Payload__Output); + /** + * Whether SimpleResponse should include username. + */ + 'fill_username': (boolean); + /** + * Whether SimpleResponse should include OAuth scope. + */ + 'fill_oauth_scope': (boolean); + /** + * 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. + */ + 'response_compressed'?: (_grpc_testing_BoolValue__Output); + /** + * Whether server should return a given status + */ + 'response_status'?: (_grpc_testing_EchoStatus__Output); + /** + * Whether the server should expect this request to be compressed. + */ + 'expect_compressed'?: (_grpc_testing_BoolValue__Output); + /** + * Whether SimpleResponse should include server_id. + */ + 'fill_server_id': (boolean); + /** + * Whether SimpleResponse should include grpclb_route_type. + */ + 'fill_grpclb_route_type': (boolean); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleResponse.ts new file mode 100644 index 000000000..661f336ce --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleResponse.ts @@ -0,0 +1,68 @@ +// Original file: proto/grpc/testing/messages.proto + +import type { Payload as _grpc_testing_Payload, Payload__Output as _grpc_testing_Payload__Output } from '../../grpc/testing/Payload'; +import type { GrpclbRouteType as _grpc_testing_GrpclbRouteType } from '../../grpc/testing/GrpclbRouteType'; + +/** + * Unary response, as configured by the request. + */ +export interface SimpleResponse { + /** + * Payload to increase message size. + */ + 'payload'?: (_grpc_testing_Payload); + /** + * The user the request came from, for verifying authentication was + * successful when the client expected it. + */ + 'username'?: (string); + /** + * OAuth scope. + */ + 'oauth_scope'?: (string); + /** + * Server ID. This must be unique among different server instances, + * but the same across all RPC's made to a particular server instance. + */ + 'server_id'?: (string); + /** + * gRPCLB Path. + */ + 'grpclb_route_type'?: (_grpc_testing_GrpclbRouteType | keyof typeof _grpc_testing_GrpclbRouteType); + /** + * Server hostname. + */ + 'hostname'?: (string); +} + +/** + * Unary response, as configured by the request. + */ +export interface SimpleResponse__Output { + /** + * Payload to increase message size. + */ + 'payload'?: (_grpc_testing_Payload__Output); + /** + * The user the request came from, for verifying authentication was + * successful when the client expected it. + */ + 'username': (string); + /** + * OAuth scope. + */ + 'oauth_scope': (string); + /** + * Server ID. This must be unique among different server instances, + * but the same across all RPC's made to a particular server instance. + */ + 'server_id': (string); + /** + * gRPCLB Path. + */ + 'grpclb_route_type': (keyof typeof _grpc_testing_GrpclbRouteType); + /** + * Server hostname. + */ + 'hostname': (string); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallRequest.ts new file mode 100644 index 000000000..56ad2b217 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallRequest.ts @@ -0,0 +1,38 @@ +// Original file: proto/grpc/testing/messages.proto + +import type { Payload as _grpc_testing_Payload, Payload__Output as _grpc_testing_Payload__Output } from '../../grpc/testing/Payload'; +import type { BoolValue as _grpc_testing_BoolValue, BoolValue__Output as _grpc_testing_BoolValue__Output } from '../../grpc/testing/BoolValue'; + +/** + * Client-streaming request. + */ +export interface StreamingInputCallRequest { + /** + * Optional input payload sent along with the request. + */ + 'payload'?: (_grpc_testing_Payload); + /** + * 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. + */ + 'expect_compressed'?: (_grpc_testing_BoolValue); +} + +/** + * Client-streaming request. + */ +export interface StreamingInputCallRequest__Output { + /** + * Optional input payload sent along with the request. + */ + 'payload'?: (_grpc_testing_Payload__Output); + /** + * 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. + */ + 'expect_compressed'?: (_grpc_testing_BoolValue__Output); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallResponse.ts new file mode 100644 index 000000000..1703e755e --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallResponse.ts @@ -0,0 +1,22 @@ +// Original file: proto/grpc/testing/messages.proto + + +/** + * Client-streaming response. + */ +export interface StreamingInputCallResponse { + /** + * Aggregated size of payloads received from the client. + */ + 'aggregated_payload_size'?: (number); +} + +/** + * Client-streaming response. + */ +export interface StreamingInputCallResponse__Output { + /** + * Aggregated size of payloads received from the client. + */ + 'aggregated_payload_size': (number); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallRequest.ts new file mode 100644 index 000000000..52922062d --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallRequest.ts @@ -0,0 +1,56 @@ +// Original file: proto/grpc/testing/messages.proto + +import type { PayloadType as _grpc_testing_PayloadType } from '../../grpc/testing/PayloadType'; +import type { ResponseParameters as _grpc_testing_ResponseParameters, ResponseParameters__Output as _grpc_testing_ResponseParameters__Output } from '../../grpc/testing/ResponseParameters'; +import type { Payload as _grpc_testing_Payload, Payload__Output as _grpc_testing_Payload__Output } from '../../grpc/testing/Payload'; +import type { EchoStatus as _grpc_testing_EchoStatus, EchoStatus__Output as _grpc_testing_EchoStatus__Output } from '../../grpc/testing/EchoStatus'; + +/** + * Server-streaming request. + */ +export interface StreamingOutputCallRequest { + /** + * 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. + */ + 'response_type'?: (_grpc_testing_PayloadType | keyof typeof _grpc_testing_PayloadType); + /** + * Configuration for each expected response message. + */ + 'response_parameters'?: (_grpc_testing_ResponseParameters)[]; + /** + * Optional input payload sent along with the request. + */ + 'payload'?: (_grpc_testing_Payload); + /** + * Whether server should return a given status + */ + 'response_status'?: (_grpc_testing_EchoStatus); +} + +/** + * Server-streaming request. + */ +export interface StreamingOutputCallRequest__Output { + /** + * 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. + */ + 'response_type': (keyof typeof _grpc_testing_PayloadType); + /** + * Configuration for each expected response message. + */ + 'response_parameters': (_grpc_testing_ResponseParameters__Output)[]; + /** + * Optional input payload sent along with the request. + */ + 'payload'?: (_grpc_testing_Payload__Output); + /** + * Whether server should return a given status + */ + 'response_status'?: (_grpc_testing_EchoStatus__Output); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallResponse.ts new file mode 100644 index 000000000..19ab306dd --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallResponse.ts @@ -0,0 +1,23 @@ +// Original file: proto/grpc/testing/messages.proto + +import type { Payload as _grpc_testing_Payload, Payload__Output as _grpc_testing_Payload__Output } from '../../grpc/testing/Payload'; + +/** + * Server-streaming response, as configured by the request and parameters. + */ +export interface StreamingOutputCallResponse { + /** + * Payload to increase response size. + */ + 'payload'?: (_grpc_testing_Payload); +} + +/** + * Server-streaming response, as configured by the request and parameters. + */ +export interface StreamingOutputCallResponse__Output { + /** + * Payload to increase response size. + */ + 'payload'?: (_grpc_testing_Payload__Output); +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/TestService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/TestService.ts new file mode 100644 index 000000000..dbb606c83 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/TestService.ts @@ -0,0 +1,202 @@ +// Original file: proto/grpc/testing/test.proto + +import type * as grpc from '@grpc/grpc-js' +import type { Empty as _grpc_testing_Empty, Empty__Output as _grpc_testing_Empty__Output } from '../../grpc/testing/Empty'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; +import type { StreamingInputCallRequest as _grpc_testing_StreamingInputCallRequest, StreamingInputCallRequest__Output as _grpc_testing_StreamingInputCallRequest__Output } from '../../grpc/testing/StreamingInputCallRequest'; +import type { StreamingInputCallResponse as _grpc_testing_StreamingInputCallResponse, StreamingInputCallResponse__Output as _grpc_testing_StreamingInputCallResponse__Output } from '../../grpc/testing/StreamingInputCallResponse'; +import type { StreamingOutputCallRequest as _grpc_testing_StreamingOutputCallRequest, StreamingOutputCallRequest__Output as _grpc_testing_StreamingOutputCallRequest__Output } from '../../grpc/testing/StreamingOutputCallRequest'; +import type { StreamingOutputCallResponse as _grpc_testing_StreamingOutputCallResponse, StreamingOutputCallResponse__Output as _grpc_testing_StreamingOutputCallResponse__Output } from '../../grpc/testing/StreamingOutputCallResponse'; + +/** + * A simple service to test the various types of RPCs and experiment with + * performance with various types of payload. + */ +export interface TestServiceClient extends grpc.Client { + /** + * 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. + */ + CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + /** + * 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. + */ + cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * One empty request followed by one empty response. + */ + EmptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + EmptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + EmptyCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + EmptyCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + /** + * One empty request followed by one empty response. + */ + emptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + emptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + emptyCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + emptyCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + + /** + * 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. + */ + FullDuplexCall(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse__Output>; + FullDuplexCall(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse__Output>; + /** + * 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. + */ + fullDuplexCall(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse__Output>; + fullDuplexCall(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse__Output>; + + /** + * 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. + */ + HalfDuplexCall(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse__Output>; + HalfDuplexCall(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse__Output>; + /** + * 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. + */ + halfDuplexCall(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse__Output>; + halfDuplexCall(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse__Output>; + + /** + * A sequence of requests followed by one response (streamed upload). + * The server returns the aggregated size of client payload as the result. + */ + StreamingInputCall(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + StreamingInputCall(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + StreamingInputCall(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + StreamingInputCall(callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + /** + * A sequence of requests followed by one response (streamed upload). + * The server returns the aggregated size of client payload as the result. + */ + streamingInputCall(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + streamingInputCall(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + streamingInputCall(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + streamingInputCall(callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + + /** + * One request followed by a sequence of responses (streamed download). + * The server returns the payload with client desired type and sizes. + */ + StreamingOutputCall(argument: _grpc_testing_StreamingOutputCallRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_StreamingOutputCallResponse__Output>; + StreamingOutputCall(argument: _grpc_testing_StreamingOutputCallRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_StreamingOutputCallResponse__Output>; + /** + * One request followed by a sequence of responses (streamed download). + * The server returns the payload with client desired type and sizes. + */ + streamingOutputCall(argument: _grpc_testing_StreamingOutputCallRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_StreamingOutputCallResponse__Output>; + streamingOutputCall(argument: _grpc_testing_StreamingOutputCallRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_StreamingOutputCallResponse__Output>; + + /** + * One request followed by one response. + */ + UnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + UnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + UnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + UnaryCall(argument: _grpc_testing_SimpleRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + /** + * One request followed by one response. + */ + unaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + unaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + unaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + unaryCall(argument: _grpc_testing_SimpleRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * The test server will not implement this method. It will be used + * to test the behavior when clients call unimplemented methods. + */ + UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + /** + * The test server will not implement this method. It will be used + * to test the behavior when clients call unimplemented methods. + */ + unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + +} + +/** + * A simple service to test the various types of RPCs and experiment with + * performance with various types of payload. + */ +export interface TestServiceHandlers extends grpc.UntypedServiceImplementation { + /** + * 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. + */ + CacheableUnaryCall: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + /** + * One empty request followed by one empty response. + */ + EmptyCall: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_Empty>; + + /** + * 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. + */ + FullDuplexCall: grpc.handleBidiStreamingCall<_grpc_testing_StreamingOutputCallRequest__Output, _grpc_testing_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. + */ + HalfDuplexCall: grpc.handleBidiStreamingCall<_grpc_testing_StreamingOutputCallRequest__Output, _grpc_testing_StreamingOutputCallResponse>; + + /** + * A sequence of requests followed by one response (streamed upload). + * The server returns the aggregated size of client payload as the result. + */ + StreamingInputCall: grpc.handleClientStreamingCall<_grpc_testing_StreamingInputCallRequest__Output, _grpc_testing_StreamingInputCallResponse>; + + /** + * One request followed by a sequence of responses (streamed download). + * The server returns the payload with client desired type and sizes. + */ + StreamingOutputCall: grpc.handleServerStreamingCall<_grpc_testing_StreamingOutputCallRequest__Output, _grpc_testing_StreamingOutputCallResponse>; + + /** + * One request followed by one response. + */ + UnaryCall: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + /** + * The test server will not implement this method. It will be used + * to test the behavior when clients call unimplemented methods. + */ + UnimplementedCall: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_Empty>; + +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/UnimplementedService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/UnimplementedService.ts new file mode 100644 index 000000000..d21dfcd0f --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/UnimplementedService.ts @@ -0,0 +1,38 @@ +// Original file: proto/grpc/testing/test.proto + +import type * as grpc from '@grpc/grpc-js' +import type { Empty as _grpc_testing_Empty, Empty__Output as _grpc_testing_Empty__Output } from '../../grpc/testing/Empty'; + +/** + * A simple service NOT implemented at servers so clients can test for + * that case. + */ +export interface UnimplementedServiceClient extends grpc.Client { + /** + * A call that no server should implement + */ + UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + /** + * A call that no server should implement + */ + unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + +} + +/** + * A simple service NOT implemented at servers so clients can test for + * that case. + */ +export interface UnimplementedServiceHandlers extends grpc.UntypedServiceImplementation { + /** + * A call that no server should implement + */ + UnimplementedCall: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_Empty>; + +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateClientConfigureService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateClientConfigureService.ts new file mode 100644 index 000000000..22947619c --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateClientConfigureService.ts @@ -0,0 +1,37 @@ +// Original file: proto/grpc/testing/test.proto + +import type * as grpc from '@grpc/grpc-js' +import type { ClientConfigureRequest as _grpc_testing_ClientConfigureRequest, ClientConfigureRequest__Output as _grpc_testing_ClientConfigureRequest__Output } from '../../grpc/testing/ClientConfigureRequest'; +import type { ClientConfigureResponse as _grpc_testing_ClientConfigureResponse, ClientConfigureResponse__Output as _grpc_testing_ClientConfigureResponse__Output } from '../../grpc/testing/ClientConfigureResponse'; + +/** + * A service to dynamically update the configuration of an xDS test client. + */ +export interface XdsUpdateClientConfigureServiceClient extends grpc.Client { + /** + * Update the tes client's configuration. + */ + Configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + Configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + Configure(argument: _grpc_testing_ClientConfigureRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + Configure(argument: _grpc_testing_ClientConfigureRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Update the tes client's configuration. + */ + configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + configure(argument: _grpc_testing_ClientConfigureRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + configure(argument: _grpc_testing_ClientConfigureRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + +} + +/** + * A service to dynamically update the configuration of an xDS test client. + */ +export interface XdsUpdateClientConfigureServiceHandlers extends grpc.UntypedServiceImplementation { + /** + * Update the tes client's configuration. + */ + Configure: grpc.handleUnaryCall<_grpc_testing_ClientConfigureRequest__Output, _grpc_testing_ClientConfigureResponse>; + +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateHealthService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateHealthService.ts new file mode 100644 index 000000000..aa1e35dca --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateHealthService.ts @@ -0,0 +1,38 @@ +// Original file: proto/grpc/testing/test.proto + +import type * as grpc from '@grpc/grpc-js' +import type { Empty as _grpc_testing_Empty, Empty__Output as _grpc_testing_Empty__Output } from '../../grpc/testing/Empty'; + +/** + * A service to remotely control health status of an xDS test server. + */ +export interface XdsUpdateHealthServiceClient extends grpc.Client { + SetNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + SetNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + SetNotServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + SetNotServing(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + setNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + setNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + setNotServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + setNotServing(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + + SetServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + SetServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + SetServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + SetServing(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + setServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + setServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + setServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + setServing(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + +} + +/** + * A service to remotely control health status of an xDS test server. + */ +export interface XdsUpdateHealthServiceHandlers extends grpc.UntypedServiceImplementation { + SetNotServing: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_Empty>; + + SetServing: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_Empty>; + +} diff --git a/packages/grpc-js-xds/interop/generated/test.ts b/packages/grpc-js-xds/interop/generated/test.ts new file mode 100644 index 000000000..f91f0c970 --- /dev/null +++ b/packages/grpc-js-xds/interop/generated/test.ts @@ -0,0 +1,68 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { LoadBalancerStatsServiceClient as _grpc_testing_LoadBalancerStatsServiceClient } from './grpc/testing/LoadBalancerStatsService'; +import type { ReconnectServiceClient as _grpc_testing_ReconnectServiceClient } from './grpc/testing/ReconnectService'; +import type { TestServiceClient as _grpc_testing_TestServiceClient } from './grpc/testing/TestService'; +import type { UnimplementedServiceClient as _grpc_testing_UnimplementedServiceClient } from './grpc/testing/UnimplementedService'; +import type { XdsUpdateClientConfigureServiceClient as _grpc_testing_XdsUpdateClientConfigureServiceClient } from './grpc/testing/XdsUpdateClientConfigureService'; +import type { XdsUpdateHealthServiceClient as _grpc_testing_XdsUpdateHealthServiceClient } from './grpc/testing/XdsUpdateHealthService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + grpc: { + testing: { + BoolValue: MessageTypeDefinition + ClientConfigureRequest: MessageTypeDefinition + ClientConfigureResponse: MessageTypeDefinition + EchoStatus: MessageTypeDefinition + Empty: MessageTypeDefinition + GrpclbRouteType: EnumTypeDefinition + LoadBalancerAccumulatedStatsRequest: MessageTypeDefinition + LoadBalancerAccumulatedStatsResponse: MessageTypeDefinition + LoadBalancerStatsRequest: MessageTypeDefinition + LoadBalancerStatsResponse: MessageTypeDefinition + /** + * A service used to obtain stats for verifying LB behavior. + */ + LoadBalancerStatsService: SubtypeConstructor & { service: ServiceDefinition } + Payload: MessageTypeDefinition + PayloadType: EnumTypeDefinition + ReconnectInfo: MessageTypeDefinition + ReconnectParams: MessageTypeDefinition + /** + * A service used to control reconnect server. + */ + ReconnectService: SubtypeConstructor & { service: ServiceDefinition } + ResponseParameters: MessageTypeDefinition + SimpleRequest: MessageTypeDefinition + SimpleResponse: MessageTypeDefinition + StreamingInputCallRequest: MessageTypeDefinition + StreamingInputCallResponse: MessageTypeDefinition + StreamingOutputCallRequest: MessageTypeDefinition + StreamingOutputCallResponse: MessageTypeDefinition + /** + * A simple service to test the various types of RPCs and experiment with + * performance with various types of payload. + */ + TestService: SubtypeConstructor & { service: ServiceDefinition } + /** + * A simple service NOT implemented at servers so clients can test for + * that case. + */ + UnimplementedService: SubtypeConstructor & { service: ServiceDefinition } + /** + * A service to dynamically update the configuration of an xDS test client. + */ + XdsUpdateClientConfigureService: SubtypeConstructor & { service: ServiceDefinition } + /** + * A service to remotely control health status of an xDS test server. + */ + XdsUpdateHealthService: SubtypeConstructor & { service: ServiceDefinition } + } + } +} + diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts new file mode 100644 index 000000000..0394c0ace --- /dev/null +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -0,0 +1,465 @@ +/* + * Copyright 2020 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. + * + */ + +import * as grpc from '@grpc/grpc-js'; + +import * as grpc_xds from '../src'; + +import { ProtoGrpcType } from './generated/test'; + +import * as protoLoader from '@grpc/proto-loader'; +import { TestServiceClient } from './generated/grpc/testing/TestService'; +import { LoadBalancerStatsResponse, _grpc_testing_LoadBalancerStatsResponse_RpcsByPeer__Output } from './generated/grpc/testing/LoadBalancerStatsResponse'; +import * as yargs from 'yargs'; +import { LoadBalancerStatsServiceHandlers } from './generated/grpc/testing/LoadBalancerStatsService'; +import { XdsUpdateClientConfigureServiceHandlers } from './generated/grpc/testing/XdsUpdateClientConfigureService'; +import { Empty__Output } from './generated/grpc/testing/Empty'; +import { LoadBalancerAccumulatedStatsResponse } from './generated/grpc/testing/LoadBalancerAccumulatedStatsResponse'; + +grpc_xds.register(); + +const packageDefinition = protoLoader.loadSync('grpc/testing/test.proto', { + keepCase: true, + defaults: true, + oneofs: true, + json: true, + longs: String, + enums: String, + includeDirs: [__dirname + '/../../proto'] +}); + +const loadedProto = grpc.loadPackageDefinition(packageDefinition) as unknown as ProtoGrpcType; + +const REQUEST_TIMEOUT_SEC = 20; + +const VERBOSITY = Number.parseInt(process.env.NODE_XDS_INTEROP_VERBOSITY ?? '0'); + +interface CallEndNotifier { + onCallSucceeded(methodName: string, peerName: string): void; + onCallFailed(message: string): void; +} + +class CallSubscriber { + private callsStarted = 0; + private callsSucceededByPeer: {[peer: string]: number} = {}; + private callsSucceededByMethod: {[method: string]: _grpc_testing_LoadBalancerStatsResponse_RpcsByPeer__Output} = {} + private callsSucceeded = 0; + private callsFinished = 0; + private failureMessageCount: Map = new Map(); + + constructor(private callGoal: number, private onFinished: () => void) {} + + addCallStarted(): void { + if (VERBOSITY >= 2) { + console.log('Call started'); + } + this.callsStarted += 1; + } + + private maybeOnFinished() { + if (this.callsFinished == this.callGoal) { + this.onFinished(); + } + } + + addCallSucceeded(methodName: string, peerName: string): void { + if (VERBOSITY >= 2) { + console.log(`Call ${methodName} to ${peerName} succeeded`); + } + if (methodName in this.callsSucceededByMethod) { + if (peerName in this.callsSucceededByMethod[methodName].rpcs_by_peer) { + this.callsSucceededByMethod[methodName].rpcs_by_peer[peerName] += 1; + } else { + this.callsSucceededByMethod[methodName].rpcs_by_peer[peerName] = 1; + } + } else { + this.callsSucceededByMethod[methodName] = {rpcs_by_peer: {[peerName]: 1}}; + } + if (peerName in this.callsSucceededByPeer) { + this.callsSucceededByPeer[peerName] += 1; + } else { + this.callsSucceededByPeer[peerName] = 1; + } + this.callsSucceeded += 1; + this.callsFinished += 1; + this.maybeOnFinished(); + } + addCallFailed(message: string): void { + if (VERBOSITY >= 2) { + console.log(`Call failed with message ${message}`); + } + this.callsFinished += 1; + this.failureMessageCount.set(message, (this.failureMessageCount.get(message) ?? 0) + 1); + this.maybeOnFinished(); + } + + needsMoreCalls(): boolean { + return this.callsStarted < this.callGoal; + } + + getFinalStats(): LoadBalancerStatsResponse { + if (VERBOSITY >= 1) { + console.log(`Out of a total of ${this.callGoal} calls requested, ${this.callsFinished} finished. ${this.callsSucceeded} succeeded`); + for (const [message, count] of this.failureMessageCount) { + console.log(`${count} failed with the message ${message}`); + } + } + return { + rpcs_by_peer: this.callsSucceededByPeer, + num_failures: this.callsStarted - this.callsSucceeded, + rpcs_by_method: this.callsSucceededByMethod + }; + } +} + +class CallStatsTracker { + + private subscribers: CallSubscriber[] = []; + + private removeSubscriber(subscriber: CallSubscriber) { + const index = this.subscribers.indexOf(subscriber); + if (index >= 0) { + this.subscribers.splice(index, 1); + } + } + + getCallStats(callCount: number, timeoutSec: number): Promise { + return new Promise((resolve, reject) => { + let finished = false; + const subscriber = new CallSubscriber(callCount, () => { + if (!finished) { + finished = true; + resolve(subscriber.getFinalStats()); + } + }); + setTimeout(() => { + if (!finished) { + finished = true; + this.removeSubscriber(subscriber); + resolve(subscriber.getFinalStats()); + } + }, timeoutSec * 1000) + this.subscribers.push(subscriber); + }) + } + + startCall(): CallEndNotifier { + const callSubscribers = this.subscribers.slice(); + for (const subscriber of callSubscribers) { + subscriber.addCallStarted(); + if (!subscriber.needsMoreCalls()) { + this.removeSubscriber(subscriber); + } + } + return { + onCallSucceeded: (methodName: string, peerName: string) => { + for (const subscriber of callSubscribers) { + subscriber.addCallSucceeded(methodName, peerName); + } + }, + onCallFailed: (message: string) => { + for (const subscriber of callSubscribers) { + subscriber.addCallFailed(message); + } + } + } + } +} + +class RecentTimestampList { + private timeList: bigint[] = []; + private nextIndex = 0; + + constructor(private readonly size: number) {} + + isFull() { + return this.timeList.length === this.size; + } + + insertTimestamp(timestamp: bigint) { + this.timeList[this.nextIndex] = timestamp; + this.nextIndex = (this.nextIndex + 1) % this.size; + } + + getSpan(): bigint { + const lastIndex = (this.nextIndex + this.size - 1) % this.size; + return this.timeList[lastIndex] - this.timeList[this.nextIndex]; + } +} + +type CallType = 'EmptyCall' | 'UnaryCall'; + +interface ClientConfiguration { + callTypes: (CallType)[]; + metadata: { + EmptyCall: grpc.Metadata, + UnaryCall: grpc.Metadata + }, + timeoutSec: number +} + +const currentConfig: ClientConfiguration = { + callTypes: ['UnaryCall'], + metadata: { + EmptyCall: new grpc.Metadata(), + UnaryCall: new grpc.Metadata() + }, + timeoutSec: REQUEST_TIMEOUT_SEC +}; + +let anyCallSucceeded = false; + +const accumulatedStats: LoadBalancerAccumulatedStatsResponse = { + num_rpcs_started_by_method: { + EMPTY_CALL: 0, + UNARY_CALL: 0 + }, + num_rpcs_succeeded_by_method: { + EMPTY_CALL: 0, + UNARY_CALL: 0 + }, + num_rpcs_failed_by_method: { + EMPTY_CALL: 0, + UNARY_CALL: 0 + }, + stats_per_method: { + EMPTY_CALL: { + rpcs_started: 0, + result: {} + }, + UNARY_CALL: { + rpcs_started: 0, + result: {} + } + } +}; + +function addAccumulatedCallStarted(callName: string) { + accumulatedStats.stats_per_method![callName].rpcs_started! += 1; + accumulatedStats.num_rpcs_started_by_method![callName] += 1; +} + +function addAccumulatedCallEnded(callName: string, result: grpc.status) { + accumulatedStats.stats_per_method![callName].result![result] = (accumulatedStats.stats_per_method![callName].result![result] ?? 0) + 1; + if (result === grpc.status.OK) { + accumulatedStats.num_rpcs_succeeded_by_method![callName] += 1; + } else { + accumulatedStats.num_rpcs_failed_by_method![callName] += 1; + } +} + +const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = { + UnaryCall: {}, + EmptyCall: {} +} + +/** + * Timestamps output by process.hrtime.bigint() are a bigint number of + * nanoseconds. This is the representation of 1 second in that context. + */ +const TIMESTAMP_ONE_SECOND = BigInt(1e9); + +function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker, callStartTimestamps: RecentTimestampList) { + const callEnumName = callTypeEnumMapReverse[type]; + addAccumulatedCallStarted(callEnumName); + const notifier = callStatsTracker.startCall(); + let gotMetadata: boolean = false; + let hostname: string | null = null; + let completed: boolean = false; + let completedWithError: boolean = false; + const startTime = process.hrtime.bigint(); + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + currentConfig.timeoutSec); + const callback = (error: grpc.ServiceError | undefined, value: Empty__Output | undefined) => { + const statusCode = error?.code ?? grpc.status.OK; + const duration = process.hrtime.bigint() - startTime; + const durationSeconds = Number(duration / TIMESTAMP_ONE_SECOND) | 0; + if (!callTimeHistogram[type][statusCode]) { + callTimeHistogram[type][statusCode] = []; + } + if (callTimeHistogram[type][statusCode][durationSeconds]) { + callTimeHistogram[type][statusCode][durationSeconds] += 1; + } else { + callTimeHistogram[type][statusCode][durationSeconds] = 1; + } + addAccumulatedCallEnded(callEnumName, statusCode); + if (error) { + if (failOnFailedRpcs && anyCallSucceeded) { + console.error('A call failed after a call succeeded'); + process.exit(1); + } + completed = true; + completedWithError = true; + notifier.onCallFailed(error.message); + } else { + anyCallSucceeded = true; + if (gotMetadata) { + if (hostname === null) { + notifier.onCallFailed('Hostname omitted from call metadata'); + } else { + notifier.onCallSucceeded(type, hostname); + } + } + } + }; + const method = (type === 'EmptyCall' ? client.emptyCall : client.unaryCall).bind(client); + const call = method({}, currentConfig.metadata[type], {deadline}, callback); + call.on('metadata', (metadata) => { + hostname = (metadata.get('hostname') as string[])[0] ?? null; + gotMetadata = true; + if (completed && !completedWithError) { + if (hostname === null) { + notifier.onCallFailed('Hostname omitted from call metadata'); + } else { + notifier.onCallSucceeded(type, hostname); + } + } + }); + /* callStartTimestamps tracks the last N timestamps of started calls, where N + * is the target QPS. If the measured span of time between the first and last + * of those N calls is greater than 1 second, we make another call + * ~immediately to correct for that. */ + callStartTimestamps.insertTimestamp(startTime); + if (callStartTimestamps.isFull()) { + if (callStartTimestamps.getSpan() > TIMESTAMP_ONE_SECOND) { + setImmediate(() => { + makeSingleRequest(client, type, failOnFailedRpcs, callStatsTracker, callStartTimestamps); + }); + } + } +} + +function sendConstantQps(client: TestServiceClient, qps: number, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { + const callStartTimestampsTrackers: {[callType: string]: RecentTimestampList} = {}; + for (const callType of ['EmptyCall', 'UnaryCall']) { + callStartTimestampsTrackers[callType] = new RecentTimestampList(qps); + } + setInterval(() => { + for (const callType of currentConfig.callTypes) { + makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker, callStartTimestampsTrackers[callType]); + } + }, 1000/qps); + setInterval(() => { + console.log(`Accumulated stats: ${JSON.stringify(accumulatedStats, undefined, 2)}`); + }, 1000); +} + +const callTypeEnumMap = { + 'EMPTY_CALL': 'EmptyCall' as CallType, + 'UNARY_CALL': 'UnaryCall' as CallType +}; + +const callTypeEnumMapReverse = { + 'EmptyCall': 'EMPTY_CALL', + 'UnaryCall': 'UNARY_CALL' +} + +const DEFAULT_TIMEOUT_SEC = 20; + +function main() { + const argv = yargs + .string(['fail_on_failed_rpcs', 'server', 'stats_port', 'rpc', 'metadata']) + .number(['num_channels', 'qps', 'rpc_timeout_sec']) + .demandOption(['server', 'stats_port']) + .default('num_channels', 1) + .default('qps', 1) + .default('rpc', 'UnaryCall') + .default('metadata', '') + .default('rpc_timeout_sec', DEFAULT_TIMEOUT_SEC) + .argv; + console.log('Starting xDS interop client. Args: ', argv); + currentConfig.callTypes = argv.rpc.split(',').filter(value => value === 'EmptyCall' || value === 'UnaryCall') as CallType[]; + currentConfig.timeoutSec = argv.rpc_timeout_sec; + for (const item of argv.metadata.split(',')) { + const [method, key, value] = item.split(':'); + if (value === undefined) { + continue; + } + if (method !== 'EmptyCall' && method !== 'UnaryCall') { + continue; + } + currentConfig.metadata[method].add(key, value); + } + console.log('EmptyCall metadata: ' + JSON.stringify(currentConfig.metadata.EmptyCall.getMap())); + console.log('UnaryCall metadata: ' + JSON.stringify(currentConfig.metadata.UnaryCall.getMap())); + const callStatsTracker = new CallStatsTracker(); + for (let i = 0; i < argv.num_channels; i++) { + /* The 'unique' channel argument is there solely to ensure that the + * channels do not share any subchannels. It does not have any + * inherent function. */ + console.log(`Interop client channel ${i} starting sending ${argv.qps} QPS to ${argv.server}`); + sendConstantQps(new loadedProto.grpc.testing.TestService(argv.server, grpc.credentials.createInsecure(), {'unique': i}), + argv.qps, + argv.fail_on_failed_rpcs === 'true', + callStatsTracker); + } + + const loadBalancerStatsServiceImpl: LoadBalancerStatsServiceHandlers = { + GetClientStats: (call, callback) => { + console.log(`Received stats request with num_rpcs=${call.request.num_rpcs} and timeout_sec=${call.request.num_rpcs}`); + callStatsTracker.getCallStats(call.request.num_rpcs, call.request.timeout_sec).then((value) => { + console.log(`Sending stats response: ${JSON.stringify(value)}`); + callback(null, value); + }, (error) => { + callback({code: grpc.status.ABORTED, details: 'Call stats collection failed'}); + }); + }, + GetClientAccumulatedStats: (call, callback) => { + console.log(`Sending accumulated stats response: ${JSON.stringify(accumulatedStats)}`); + console.log(`Call durations: ${JSON.stringify(callTimeHistogram, undefined, 2)}`); + callback(null, accumulatedStats); + } + } + + const xdsUpdateClientConfigureServiceImpl: XdsUpdateClientConfigureServiceHandlers = { + Configure: (call, callback) => { + console.log('Received new client configuration: ' + JSON.stringify(call.request, undefined, 2)); + const callMetadata = { + EmptyCall: new grpc.Metadata(), + UnaryCall: new grpc.Metadata() + }; + for (const metadataItem of call.request.metadata) { + callMetadata[callTypeEnumMap[metadataItem.type]].add(metadataItem.key, metadataItem.value); + } + currentConfig.callTypes = call.request.types.map(value => callTypeEnumMap[value]); + currentConfig.metadata = callMetadata; + if (call.request.timeout_sec > 0) { + currentConfig.timeoutSec = call.request.timeout_sec; + } else { + currentConfig.timeoutSec = DEFAULT_TIMEOUT_SEC; + } + console.log('Updated to new client configuration: ' + JSON.stringify(currentConfig, undefined, 2)); + callback(null, {}); + } + } + + const server = new grpc.Server(); + server.addService(loadedProto.grpc.testing.LoadBalancerStatsService.service, loadBalancerStatsServiceImpl); + server.addService(loadedProto.grpc.testing.XdsUpdateClientConfigureService.service, xdsUpdateClientConfigureServiceImpl); + grpc.addAdminServicesToServer(server); + server.bindAsync(`0.0.0.0:${argv.stats_port}`, grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + throw error; + } + console.log(`Starting stats service server bound to port ${port}`); + server.start(); + }); +} + +if (require.main === module) { + main(); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json new file mode 100644 index 000000000..7c735b652 --- /dev/null +++ b/packages/grpc-js-xds/package.json @@ -0,0 +1,75 @@ +{ + "name": "@grpc/grpc-js-xds", + "version": "1.8.2", + "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", + "main": "build/src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "check": "gts check", + "clean": "gts clean", + "compile": "tsc", + "fix": "gts fix", + "prepare": "npm run compile", + "pretest": "npm run compile", + "posttest": "npm run check", + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto envoy/service/status/v3/csds.proto", + "generate-interop-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O interop/generated --grpcLib @grpc/grpc-js grpc/testing/test.proto", + "generate-test-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O test/generated --grpcLib @grpc/grpc-js grpc/testing/echo.proto" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/grpc/grpc-node.git" + }, + "keywords": [ + "grpc" + ], + "author": { + "name": "Google Inc." + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/grpc/grpc-node/issues" + }, + "homepage": "https://github.com/grpc/grpc-node#readme", + "devDependencies": { + "@grpc/grpc-js": "file:../grpc-js", + "@types/gulp": "^4.0.6", + "@types/gulp-mocha": "0.0.32", + "@types/mocha": "^5.2.6", + "@types/node": "^13.11.1", + "@types/yargs": "^15.0.5", + "gts": "^2.0.2", + "typescript": "^3.8.3", + "yargs": "^15.4.1" + }, + "dependencies": { + "@grpc/proto-loader": "^0.6.0", + "google-auth-library": "^7.0.2", + "re2-wasm": "^1.0.1" + }, + "peerDependencies": { + "@grpc/grpc-js": "~1.8.0" + }, + "engines": { + "node": ">=10.10.0" + }, + "files": [ + "src/**/*.ts", + "build/src/**/*.{js,d.ts,js.map}", + "deps/envoy-api/envoy/admin/v3/**/*.proto", + "deps/envoy-api/envoy/config/**/*.proto", + "deps/envoy-api/envoy/service/**/*.proto", + "deps/envoy-api/envoy/type/**/*.proto", + "deps/envoy-api/envoy/annotations/**/*.proto", + "deps/envoy-api/envoy/extensions/**/*.proto", + "deps/googleapis/google/api/**/*.proto", + "deps/googleapis/google/protobuf/**/*.proto", + "deps/googleapis/google/rpc/**/*.proto", + "deps/xds/udpa/annotations/**/*.proto", + "deps/xds/udpa/type/**/*.proto", + "deps/xds/xds/annotations/**/*.proto", + "deps/xds/xds/core/**/*.proto", + "deps/xds/xds/type/**/*.proto", + "deps/protoc-gen-validate/validate/**/*.proto" + ] +} diff --git a/packages/grpc-js-xds/proto/grpc/testing/echo.proto b/packages/grpc-js-xds/proto/grpc/testing/echo.proto new file mode 100644 index 000000000..7f444b43f --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/echo.proto @@ -0,0 +1,70 @@ + +// 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"; + +package grpc.testing; + +import "grpc/testing/echo_messages.proto"; +import "grpc/testing/simple_messages.proto"; + +service EchoTestService { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + rpc CheckDeadlineUpperBound(SimpleRequest) returns (StringValue); + rpc CheckDeadlineSet(SimpleRequest) returns (StringValue); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); + rpc UnimplementedBidi(stream EchoRequest) returns (stream EchoResponse); +} + +service EchoTest1Service { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +service EchoTest2Service { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +service UnimplementedEchoService { + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +// A service without any rpc defined to test coverage. +service NoRpcService {} diff --git a/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto b/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto new file mode 100644 index 000000000..44f22133e --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto @@ -0,0 +1,74 @@ + +// 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"; + +package grpc.testing; + +option cc_enable_arenas = true; + +import "grpc/testing/xds/v3/orca_load_report.proto"; + +// Message to be echoed back serialized in trailer. +message DebugInfo { + repeated string stack_entries = 1; + string detail = 2; +} + +// Error status client expects to see. +message ErrorStatus { + int32 code = 1; + string error_message = 2; + string binary_error_details = 3; +} + +message RequestParams { + bool echo_deadline = 1; + int32 client_cancel_after_us = 2; + int32 server_cancel_after_us = 3; + bool echo_metadata = 4; + bool check_auth_context = 5; + int32 response_message_length = 6; + bool echo_peer = 7; + string expected_client_identity = 8; // will force check_auth_context. + bool skip_cancelled_check = 9; + string expected_transport_security_type = 10; + DebugInfo debug_info = 11; + bool server_die = 12; // Server should not see a request with this set. + string binary_error_details = 13; + ErrorStatus expected_error = 14; + int32 server_sleep_us = 15; // sleep when invoking server for deadline tests + int32 backend_channel_idx = 16; // which backend to send request to + bool echo_metadata_initially = 17; + bool server_notify_client_when_started = 18; + xds.data.orca.v3.OrcaLoadReport backend_metrics = 19; + bool echo_host_from_authority_header = 20; +} + +message EchoRequest { + string message = 1; + RequestParams param = 2; +} + +message ResponseParams { + int64 request_deadline = 1; + string host = 2; + string peer = 3; +} + +message EchoResponse { + string message = 1; + ResponseParams param = 2; +} diff --git a/packages/grpc-js-xds/proto/grpc/testing/empty.proto b/packages/grpc-js-xds/proto/grpc/testing/empty.proto new file mode 100644 index 000000000..6a0aa88df --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/empty.proto @@ -0,0 +1,28 @@ + +// 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"; + +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/packages/grpc-js-xds/proto/grpc/testing/messages.proto b/packages/grpc-js-xds/proto/grpc/testing/messages.proto new file mode 100644 index 000000000..559876ed7 --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/messages.proto @@ -0,0 +1,270 @@ + +// Copyright 2015-2016 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. + +// 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; +} + +// 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 { + // 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; +} + +// The type of route that a client took to reach a server w.r.t. gRPCLB. +// The server must fill in "fallback" if it detects that the RPC reached +// the server via the "gRPCLB fallback" path, and "backend" if it detects +// that the RPC reached the server via "gRPCLB backend" path (i.e. if it got +// the address of this server from the gRPCLB server BalanceLoad RPC). Exactly +// how this detection is done is context and server dependent. +enum GrpclbRouteType { + // Server didn't detect the route that a client took to reach it. + GRPCLB_ROUTE_TYPE_UNKNOWN = 0; + // Indicates that a client reached a server via gRPCLB fallback. + GRPCLB_ROUTE_TYPE_FALLBACK = 1; + // Indicates that a client reached a server as a gRPCLB-given backend. + GRPCLB_ROUTE_TYPE_BACKEND = 2; +} + +// Unary request. +message SimpleRequest { + // 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; + + // Whether SimpleResponse should include server_id. + bool fill_server_id = 9; + + // Whether SimpleResponse should include grpclb_route_type. + bool fill_grpclb_route_type = 10; +} + +// 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; + + // Server ID. This must be unique among different server instances, + // but the same across all RPC's made to a particular server instance. + string server_id = 4; + // gRPCLB Path. + GrpclbRouteType grpclb_route_type = 5; + + // Server hostname. + string hostname = 6; +} + +// 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 { + // 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; +} + +message LoadBalancerStatsRequest { + // Request stats for the next num_rpcs sent by client. + int32 num_rpcs = 1; + // If num_rpcs have not completed within timeout_sec, return partial results. + int32 timeout_sec = 2; +} + +message LoadBalancerStatsResponse { + message RpcsByPeer { + // The number of completed RPCs for each peer. + map rpcs_by_peer = 1; + } + // The number of completed RPCs for each peer. + map rpcs_by_peer = 1; + // The number of RPCs that failed to record a remote peer. + int32 num_failures = 2; + map rpcs_by_method = 3; +} + +// Request for retrieving a test client's accumulated stats. +message LoadBalancerAccumulatedStatsRequest {} + +// Accumulated stats for RPCs sent by a test client. +message LoadBalancerAccumulatedStatsResponse { + // The total number of RPCs have ever issued for each type. + // Deprecated: use stats_per_method.rpcs_started instead. + map num_rpcs_started_by_method = 1 [deprecated = true]; + // The total number of RPCs have ever completed successfully for each type. + // Deprecated: use stats_per_method.result instead. + map num_rpcs_succeeded_by_method = 2 [deprecated = true]; + // The total number of RPCs have ever failed for each type. + // Deprecated: use stats_per_method.result instead. + map num_rpcs_failed_by_method = 3 [deprecated = true]; + + message MethodStats { + // The number of RPCs that were started for this method. + int32 rpcs_started = 1; + + // The number of RPCs that completed with each status for this method. The + // key is the integral value of a google.rpc.Code; the value is the count. + map result = 2; + } + + // Per-method RPC statistics. The key is the RpcType in string form; e.g. + // 'EMPTY_CALL' or 'UNARY_CALL' + map stats_per_method = 4; +} + +// Configurations for a test client. +message ClientConfigureRequest { + // Type of RPCs to send. + enum RpcType { + EMPTY_CALL = 0; + UNARY_CALL = 1; + } + + // Metadata to be attached for the given type of RPCs. + message Metadata { + RpcType type = 1; + string key = 2; + string value = 3; + } + + // The types of RPCs the client sends. + repeated RpcType types = 1; + // The collection of custom metadata to be attached to RPCs sent by the client. + repeated Metadata metadata = 2; + // The deadline to use, in seconds, for all RPCs. If unset or zero, the + // client will use the default from the command-line. + int32 timeout_sec = 3; +} + +// Response for updating a test client's configuration. +message ClientConfigureResponse {} diff --git a/test/api/echo_service.proto b/packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto similarity index 77% rename from test/api/echo_service.proto rename to packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto index 0b27c8b16..3afe236b4 100644 --- a/test/api/echo_service.proto +++ b/packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto @@ -1,4 +1,5 @@ -// Copyright 2015 gRPC authors. + +// Copyright 2018 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,11 +15,12 @@ syntax = "proto3"; -message EchoMessage { - string value = 1; - int32 value2 = 2; -} +package grpc.testing; + +message SimpleRequest {} + +message SimpleResponse {} -service EchoService { - rpc Echo (EchoMessage) returns (EchoMessage); +message StringValue { + string message = 1; } diff --git a/packages/grpc-js-xds/proto/grpc/testing/test.proto b/packages/grpc-js-xds/proto/grpc/testing/test.proto new file mode 100644 index 000000000..b2606a009 --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/test.proto @@ -0,0 +1,102 @@ + +// Copyright 2015-2016 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. + +// An integration test service that covers all the method signature permutations +// of unary/streaming requests/responses. + +syntax = "proto3"; + +import "grpc/testing/empty.proto"; +import "grpc/testing/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); +} + +// A service used to obtain stats for verifying LB behavior. +service LoadBalancerStatsService { + // Gets the backend distribution for RPCs sent by a test client. + rpc GetClientStats(LoadBalancerStatsRequest) + returns (LoadBalancerStatsResponse) {} + + // Gets the accumulated stats for RPCs sent by a test client. + rpc GetClientAccumulatedStats(LoadBalancerAccumulatedStatsRequest) + returns (LoadBalancerAccumulatedStatsResponse) {} +} + +// A service to remotely control health status of an xDS test server. +service XdsUpdateHealthService { + rpc SetServing(grpc.testing.Empty) returns (grpc.testing.Empty); + rpc SetNotServing(grpc.testing.Empty) returns (grpc.testing.Empty); +} + +// A service to dynamically update the configuration of an xDS test client. +service XdsUpdateClientConfigureService { + // Update the tes client's configuration. + rpc Configure(ClientConfigureRequest) returns (ClientConfigureResponse); +} diff --git a/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto b/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto new file mode 100644 index 000000000..033e64ba4 --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto @@ -0,0 +1,44 @@ +// Copyright 2020 The 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. + +// Local copy of Envoy xDS proto file, used for testing only. + +syntax = "proto3"; + +package xds.data.orca.v3; + +// See section `ORCA load report format` of the design document in +// :ref:`https://github.com/envoyproxy/envoy/issues/6614`. + +message OrcaLoadReport { + // CPU utilization expressed as a fraction of available CPU resources. This + // should be derived from the latest sample or measurement. + double cpu_utilization = 1; + + // Memory utilization expressed as a fraction of available memory + // resources. This should be derived from the latest sample or measurement. + double mem_utilization = 2; + + // Total RPS being served by an endpoint. This should cover all services that an endpoint is + // responsible for. + uint64 rps = 3; + + // Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + // storage) associated with the request. + map request_cost = 4; + + // Resource utilization values. Each value is expressed as a fraction of total resources + // available, derived from the latest sample or measurement. + map utilization = 5; +} diff --git a/packages/grpc-js-xds/scripts/psm-interop-build-node.sh b/packages/grpc-js-xds/scripts/psm-interop-build-node.sh new file mode 100755 index 000000000..d52206f0e --- /dev/null +++ b/packages/grpc-js-xds/scripts/psm-interop-build-node.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Copyright 2024 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. +set -eo pipefail + +####################################### +# Builds test app Docker images and pushes them to GCR. +# Called from psm_interop_kokoro_lib.sh. +# +# Globals: +# SRC_DIR: Absolute path to the source repo on Kokoro VM +# SERVER_IMAGE_NAME: Test server Docker image name +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# DOCKER_REGISTRY: Docker registry to push to +# Outputs: +# Writes the output of docker image build stdout, stderr +####################################### +psm::lang::build_docker_images() { + local client_dockerfile="packages/grpc-js-xds/interop/Dockerfile" + + cd "${SRC_DIR}" + psm::tools::run_verbose git submodule update --init --recursive + psm::tools::run_verbose git submodule status + + psm::build::docker_images_generic "${client_dockerfile}" +} diff --git a/packages/grpc-js-xds/scripts/psm-interop-test-node.sh b/packages/grpc-js-xds/scripts/psm-interop-test-node.sh new file mode 100755 index 000000000..169cf06f2 --- /dev/null +++ b/packages/grpc-js-xds/scripts/psm-interop-test-node.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Copyright 2024 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. +set -eo pipefail + +# Input parameters to psm:: methods of the install script. +readonly GRPC_LANGUAGE="node" +readonly BUILD_SCRIPT_DIR="$(dirname "$0")" + +# Used locally. +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" + +psm::lang::source_install_lib() { + echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" + local install_lib + # Download to a tmp file. + install_lib="$(mktemp -d)/psm_interop_kokoro_lib.sh" + curl -s --retry-connrefused --retry 5 -o "${install_lib}" "${TEST_DRIVER_INSTALL_SCRIPT_URL}" + # Checksum. + if command -v sha256sum &> /dev/null; then + echo "Install script checksum:" + sha256sum "${install_lib}" + fi + source "${install_lib}" +} + +psm::lang::source_install_lib +source "${BUILD_SCRIPT_DIR}/psm-interop-build-${GRPC_LANGUAGE}.sh" +psm::run "${PSM_TEST_SUITE}" diff --git a/packages/grpc-js-xds/scripts/xds-v3.sh b/packages/grpc-js-xds/scripts/xds-v3.sh new file mode 100755 index 000000000..103cbc429 --- /dev/null +++ b/packages/grpc-js-xds/scripts/xds-v3.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2021 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. + +XDS_V3_OPT="--xds_v3_support" $(dirname $0)/xds.sh \ No newline at end of file diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh new file mode 100755 index 000000000..7e6794a31 --- /dev/null +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# Copyright 2020 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. + +cd $(dirname $0)/.. +base=$(pwd) + +# Install NVM +cd ~ +export NVM_DIR=`pwd`/.nvm +curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.4/install.sh | bash + +# Load NVM +. $NVM_DIR/nvm.sh + +nvm install 12 + +set -exu -o pipefail +[[ -f /VERSION ]] && cat /VERSION + +# Make nvm available to the subprocess that the python script spawns +echo "source $NVM_DIR/nvm.sh" > ~/.profile +echo "source $NVM_DIR/nvm.sh" > ~/.shrc +export ENV=~/.shrc + +cd $base/../grpc-js +npm install + +# grpc-js-xds has a dev dependency on "../grpc-js", so it should pull that in automatically +cd $base +git submodule update --init --recursive +npm install + +cd ../../.. + +git clone -b master --single-branch --depth=1 https://github.com/grpc/grpc.git + +grpc/tools/run_tests/helper_scripts/prep_xds.sh + +mkdir -p "${KOKORO_ARTIFACTS_DIR}/github/grpc/reports" + +GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds \ + GRPC_NODE_VERBOSITY=DEBUG \ + NODE_XDS_INTEROP_VERBOSITY=1 \ + python3 grpc/tools/run_tests/run_xds_tests.py \ + --test_case="ping_pong,circuit_breaking" \ + --project_id=grpc-testing \ + --source_image=projects/grpc-testing/global/images/xds-test-server-5 \ + --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ + --gcp_suffix=$(date '+%s') \ + --verbose \ + --qps=75 \ + ${XDS_V3_OPT-} \ + --client_cmd="$(which node) --enable-source-maps --prof --logfile=${KOKORO_ARTIFACTS_DIR}/github/grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ + --server=xds:///{server_uri} \ + --stats_port={stats_port} \ + --qps={qps} \ + {fail_on_failed_rpc} \ + {rpcs_to_send} \ + {metadata_to_send}" diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts new file mode 100644 index 000000000..114c18042 --- /dev/null +++ b/packages/grpc-js-xds/src/csds.ts @@ -0,0 +1,214 @@ +/* + * Copyright 2021 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. + * + */ + +import { Node } from "./generated/envoy/config/core/v3/Node"; +import { ClientConfig, _envoy_service_status_v3_ClientConfig_GenericXdsConfig as GenericXdsConfig } from "./generated/envoy/service/status/v3/ClientConfig"; +import { ClientStatusDiscoveryServiceHandlers } from "./generated/envoy/service/status/v3/ClientStatusDiscoveryService"; +import { ClientStatusRequest__Output } from "./generated/envoy/service/status/v3/ClientStatusRequest"; +import { ClientStatusResponse } from "./generated/envoy/service/status/v3/ClientStatusResponse"; +import { Timestamp } from "./generated/google/protobuf/Timestamp"; +import { AdsTypeUrl, CDS_TYPE_URL, EDS_TYPE_URL, LDS_TYPE_URL, RDS_TYPE_URL } from "./resources"; +import { HandleResponseResult } from "./xds-stream-state/xds-stream-state"; +import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, status, experimental, loadPackageDefinition, logVerbosity } from '@grpc/grpc-js'; +import { loadSync } from "@grpc/proto-loader"; +import { ProtoGrpcType as CsdsProtoGrpcType } from "./generated/csds"; + +import registerAdminService = experimental.registerAdminService; + +const TRACER_NAME = 'csds'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + + +function dateToProtoTimestamp(date?: Date | null): Timestamp | null { + if (!date) { + return null; + } + const millisSinceEpoch = date.getTime(); + return { + seconds: (millisSinceEpoch / 1000) | 0, + nanos: (millisSinceEpoch % 1000) * 1_000_000 + } +} + +let clientNode: Node | null = null; + +const configStatus = { + [EDS_TYPE_URL]: new Map(), + [CDS_TYPE_URL]: new Map(), + [RDS_TYPE_URL]: new Map(), + [LDS_TYPE_URL]: new Map() +}; + +/** + * This function only accepts a v3 Node message, because we are only supporting + * v3 CSDS and it only handles v3 Nodes. If the client is actually using v2 xDS + * APIs, it should just provide the equivalent v3 Node message. + * @param node The Node message for the client that is requesting resources + */ +export function setCsdsClientNode(node: Node) { + clientNode = node; +} + +/** + * Update the config status maps from the list of names of requested resources + * for a specific type URL. These lists are the source of truth for determining + * what resources will be listed in the CSDS response. Any resource that is not + * in this list will never actually be applied anywhere. + * @param typeUrl The resource type URL + * @param names The list of resource names that are being requested + */ +export function updateCsdsRequestedNameList(typeUrl: AdsTypeUrl, names: string[]) { + trace('Update type URL ' + typeUrl + ' with names [' + names + ']'); + const currentTime = dateToProtoTimestamp(new Date()); + const configMap = configStatus[typeUrl]; + for (const name of names) { + if (!configMap.has(name)) { + configMap.set(name, { + type_url: typeUrl, + name: name, + last_updated: currentTime, + client_status: 'REQUESTED' + }); + } + } + for (const name of configMap.keys()) { + if (!names.includes(name)) { + configMap.delete(name); + } + } +} + +/** + * Update the config status maps from the result of parsing a single ADS + * response. All resources that validated are considered "ACKED", and all + * resources that failed validation are considered "NACKED". + * @param typeUrl The type URL of resources in this response + * @param versionInfo The version info field from this response + * @param updates The lists of resources that passed and failed validation + */ +export function updateCsdsResourceResponse(typeUrl: AdsTypeUrl, versionInfo: string, updates: HandleResponseResult) { + const currentTime = dateToProtoTimestamp(new Date()); + const configMap = configStatus[typeUrl]; + for (const {name, raw} of updates.accepted) { + const mapEntry = configMap.get(name); + if (mapEntry) { + trace('Updated ' + typeUrl + ' resource ' + name + ' to state ACKED'); + mapEntry.client_status = 'ACKED'; + mapEntry.version_info = versionInfo; + mapEntry.xds_config = raw; + mapEntry.error_state = null; + mapEntry.last_updated = currentTime; + } + } + for (const {name, error, raw} of updates.rejected) { + const mapEntry = configMap.get(name); + if (mapEntry) { + trace('Updated ' + typeUrl + ' resource ' + name + ' to state NACKED'); + mapEntry.client_status = 'NACKED'; + mapEntry.error_state = { + failed_configuration: raw, + last_update_attempt: currentTime, + details: error, + version_info: versionInfo + }; + } + } + for (const name of updates.missing) { + const mapEntry = configMap.get(name); + if (mapEntry) { + trace('Updated ' + typeUrl + ' resource ' + name + ' to state DOES_NOT_EXIST'); + mapEntry.client_status = 'DOES_NOT_EXIST'; + mapEntry.version_info = versionInfo; + mapEntry.xds_config = null; + mapEntry.error_state = null; + mapEntry.last_updated = currentTime; + } + } +} + +function getCurrentConfig(): ClientConfig { + const genericConfigList: GenericXdsConfig[] = []; + for (const configMap of Object.values(configStatus)) { + for (const configValue of configMap.values()) { + genericConfigList.push(configValue); + } + } + const config = { + node: clientNode, + generic_xds_configs: genericConfigList + }; + trace('Sending curent config ' + JSON.stringify(config, undefined, 2)); + return config; +} + +const csdsImplementation: ClientStatusDiscoveryServiceHandlers = { + FetchClientStatus(call: ServerUnaryCall, callback: sendUnaryData) { + const request = call.request; + if (request.node_matchers.length > 0) { + callback({ + code: status.INVALID_ARGUMENT, + details: 'Node matchers not supported' + }); + return; + } + callback(null, { + config: [getCurrentConfig()] + }); + }, + StreamClientStatus(call: ServerDuplexStream) { + call.on('data', (request: ClientStatusRequest__Output) => { + if (request.node_matchers.length > 0) { + call.emit('error', { + code: status.INVALID_ARGUMENT, + details: 'Node matchers not supported' + }); + return; + } + call.write({ + config: [getCurrentConfig()] + }); + }); + call.on('end', () => { + call.end(); + }); + } +} + +const loadedProto = loadSync('envoy/service/status/v3/csds.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/protoc-gen-validate/', + __dirname + '/../../deps/googleapis/' + ], +}); + +const csdsGrpcObject = loadPackageDefinition(loadedProto) as unknown as CsdsProtoGrpcType; +const csdsServiceDefinition = csdsGrpcObject.envoy.service.status.v3.ClientStatusDiscoveryService.service; + +export function setup() { + registerAdminService(() => csdsServiceDefinition, () => csdsImplementation); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts new file mode 100644 index 000000000..7ec0fd187 --- /dev/null +++ b/packages/grpc-js-xds/src/environment.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 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. + * + */ + +export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; +export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; +export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY ?? 'true') === 'true'; \ No newline at end of file diff --git a/packages/grpc-js-xds/src/fraction.ts b/packages/grpc-js-xds/src/fraction.ts new file mode 100644 index 000000000..709af72b2 --- /dev/null +++ b/packages/grpc-js-xds/src/fraction.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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. + */ + +import { FractionalPercent__Output } from "./generated/envoy/type/v3/FractionalPercent"; + +export interface Fraction { + numerator: number; + denominator: number; +} + +export function fractionToString(fraction: Fraction): string { + return `${fraction.numerator}/${fraction.denominator}`; +} + +const RUNTIME_FRACTION_DENOMINATOR_VALUES = { + HUNDRED: 100, + TEN_THOUSAND: 10_000, + MILLION: 1_000_000 +} + +export function envoyFractionToFraction(envoyFraction: FractionalPercent__Output): Fraction { + return { + numerator: envoyFraction.numerator, + denominator: RUNTIME_FRACTION_DENOMINATOR_VALUES[envoyFraction.denominator] + }; +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/generated/ads.ts b/packages/grpc-js-xds/src/generated/ads.ts new file mode 100644 index 000000000..228f6f1d4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/ads.ts @@ -0,0 +1,179 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v3_AggregatedDiscoveryServiceClient, AggregatedDiscoveryServiceDefinition as _envoy_service_discovery_v3_AggregatedDiscoveryServiceDefinition } from './envoy/service/discovery/v3/AggregatedDiscoveryService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + annotations: { + } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + } + service: { + discovery: { + v3: { + AdsDummy: MessageTypeDefinition + /** + * See https://github.com/lyft/envoy-api#apis for a description of the role of + * ADS and how it is intended to be used by a management server. ADS requests + * have the same structure as their singleton xDS counterparts, but can + * multiplex many resource types on a single stream. The type_url in the + * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover + * the multiplexed singleton APIs at the Envoy instance and management server. + */ + AggregatedDiscoveryService: SubtypeConstructor & { service: _envoy_service_discovery_v3_AggregatedDiscoveryServiceDefinition } + DeltaDiscoveryRequest: MessageTypeDefinition + DeltaDiscoveryResponse: MessageTypeDefinition + DiscoveryRequest: MessageTypeDefinition + DiscoveryResponse: MessageTypeDefinition + Resource: MessageTypeDefinition + } + } + } + type: { + v3: { + FractionalPercent: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + rpc: { + Status: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + ContextParams: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts new file mode 100644 index 000000000..78ac3bbd3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -0,0 +1,218 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + annotations: { + } + config: { + cluster: { + v3: { + CircuitBreakers: MessageTypeDefinition + Cluster: MessageTypeDefinition + ClusterCollection: MessageTypeDefinition + Filter: MessageTypeDefinition + LoadBalancingPolicy: MessageTypeDefinition + OutlierDetection: MessageTypeDefinition + TrackClusterStats: MessageTypeDefinition + UpstreamBindConfig: MessageTypeDefinition + UpstreamConnectionOptions: MessageTypeDefinition + } + } + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + DnsResolutionConfig: MessageTypeDefinition + DnsResolverOptions: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + EventServiceConfig: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + GrpcProtocolOptions: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HealthCheck: MessageTypeDefinition + HealthStatus: EnumTypeDefinition + Http1ProtocolOptions: MessageTypeDefinition + Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition + HttpProtocolOptions: MessageTypeDefinition + HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TcpProtocolOptions: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + UpstreamHttpProtocolOptions: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + endpoint: { + v3: { + ClusterLoadAssignment: MessageTypeDefinition + Endpoint: MessageTypeDefinition + LbEndpoint: MessageTypeDefinition + LedsClusterLocalityConfig: MessageTypeDefinition + LocalityLbEndpoints: MessageTypeDefinition + } + } + } + type: { + matcher: { + v3: { + ListStringMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } + } + v3: { + CodecClientType: EnumTypeDefinition + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + Empty: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FieldSecurityAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + CollectionEntry: MessageTypeDefinition + ContextParams: MessageTypeDefinition + ResourceLocator: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/csds.ts b/packages/grpc-js-xds/src/generated/csds.ts new file mode 100644 index 000000000..58e903bc9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/csds.ts @@ -0,0 +1,393 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { ClientStatusDiscoveryServiceClient as _envoy_service_status_v3_ClientStatusDiscoveryServiceClient, ClientStatusDiscoveryServiceDefinition as _envoy_service_status_v3_ClientStatusDiscoveryServiceDefinition } from './envoy/service/status/v3/ClientStatusDiscoveryService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + admin: { + v3: { + BootstrapConfigDump: MessageTypeDefinition + ClientResourceStatus: EnumTypeDefinition + ClustersConfigDump: MessageTypeDefinition + ConfigDump: MessageTypeDefinition + EndpointsConfigDump: MessageTypeDefinition + ListenersConfigDump: MessageTypeDefinition + RoutesConfigDump: MessageTypeDefinition + ScopedRoutesConfigDump: MessageTypeDefinition + SecretsConfigDump: MessageTypeDefinition + UpdateFailureState: MessageTypeDefinition + } + } + annotations: { + } + config: { + accesslog: { + v3: { + AccessLog: MessageTypeDefinition + AccessLogFilter: MessageTypeDefinition + AndFilter: MessageTypeDefinition + ComparisonFilter: MessageTypeDefinition + DurationFilter: MessageTypeDefinition + ExtensionFilter: MessageTypeDefinition + GrpcStatusFilter: MessageTypeDefinition + HeaderFilter: MessageTypeDefinition + MetadataFilter: MessageTypeDefinition + NotHealthCheckFilter: MessageTypeDefinition + OrFilter: MessageTypeDefinition + ResponseFlagFilter: MessageTypeDefinition + RuntimeFilter: MessageTypeDefinition + StatusCodeFilter: MessageTypeDefinition + TraceableFilter: MessageTypeDefinition + } + } + bootstrap: { + v3: { + Admin: MessageTypeDefinition + Bootstrap: MessageTypeDefinition + ClusterManager: MessageTypeDefinition + CustomInlineHeader: MessageTypeDefinition + FatalAction: MessageTypeDefinition + LayeredRuntime: MessageTypeDefinition + Runtime: MessageTypeDefinition + RuntimeLayer: MessageTypeDefinition + Watchdog: MessageTypeDefinition + Watchdogs: MessageTypeDefinition + } + } + cluster: { + v3: { + CircuitBreakers: MessageTypeDefinition + Cluster: MessageTypeDefinition + ClusterCollection: MessageTypeDefinition + Filter: MessageTypeDefinition + LoadBalancingPolicy: MessageTypeDefinition + OutlierDetection: MessageTypeDefinition + TrackClusterStats: MessageTypeDefinition + UpstreamBindConfig: MessageTypeDefinition + UpstreamConnectionOptions: MessageTypeDefinition + } + } + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + DnsResolutionConfig: MessageTypeDefinition + DnsResolverOptions: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + EventServiceConfig: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + GrpcProtocolOptions: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HealthCheck: MessageTypeDefinition + HealthStatus: EnumTypeDefinition + Http1ProtocolOptions: MessageTypeDefinition + Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition + HttpProtocolOptions: MessageTypeDefinition + HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TcpProtocolOptions: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + UdpSocketConfig: MessageTypeDefinition + UpstreamHttpProtocolOptions: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + endpoint: { + v3: { + ClusterLoadAssignment: MessageTypeDefinition + Endpoint: MessageTypeDefinition + LbEndpoint: MessageTypeDefinition + LedsClusterLocalityConfig: MessageTypeDefinition + LocalityLbEndpoints: MessageTypeDefinition + } + } + listener: { + v3: { + ActiveRawUdpListenerConfig: MessageTypeDefinition + ApiListener: MessageTypeDefinition + Filter: MessageTypeDefinition + FilterChain: MessageTypeDefinition + FilterChainMatch: MessageTypeDefinition + Listener: MessageTypeDefinition + ListenerCollection: MessageTypeDefinition + ListenerFilter: MessageTypeDefinition + ListenerFilterChainMatchPredicate: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + UdpListenerConfig: MessageTypeDefinition + } + } + metrics: { + v3: { + DogStatsdSink: MessageTypeDefinition + HistogramBucketSettings: MessageTypeDefinition + HystrixSink: MessageTypeDefinition + StatsConfig: MessageTypeDefinition + StatsMatcher: MessageTypeDefinition + StatsSink: MessageTypeDefinition + StatsdSink: MessageTypeDefinition + TagSpecifier: MessageTypeDefinition + } + } + overload: { + v3: { + BufferFactoryConfig: MessageTypeDefinition + OverloadAction: MessageTypeDefinition + OverloadManager: MessageTypeDefinition + ResourceMonitor: MessageTypeDefinition + ScaleTimersOverloadActionConfig: MessageTypeDefinition + ScaledTrigger: MessageTypeDefinition + ThresholdTrigger: MessageTypeDefinition + Trigger: MessageTypeDefinition + } + } + route: { + v3: { + CorsPolicy: MessageTypeDefinition + Decorator: MessageTypeDefinition + DirectResponseAction: MessageTypeDefinition + FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition + HeaderMatcher: MessageTypeDefinition + HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition + QueryParameterMatcher: MessageTypeDefinition + RateLimit: MessageTypeDefinition + RedirectAction: MessageTypeDefinition + RetryPolicy: MessageTypeDefinition + Route: MessageTypeDefinition + RouteAction: MessageTypeDefinition + RouteMatch: MessageTypeDefinition + Tracing: MessageTypeDefinition + VirtualCluster: MessageTypeDefinition + VirtualHost: MessageTypeDefinition + WeightedCluster: MessageTypeDefinition + } + } + trace: { + v3: { + Tracing: MessageTypeDefinition + } + } + } + extensions: { + transport_sockets: { + tls: { + v3: { + CertificateProviderPluginInstance: MessageTypeDefinition + CertificateValidationContext: MessageTypeDefinition + GenericSecret: MessageTypeDefinition + PrivateKeyProvider: MessageTypeDefinition + SdsSecretConfig: MessageTypeDefinition + Secret: MessageTypeDefinition + TlsCertificate: MessageTypeDefinition + TlsParameters: MessageTypeDefinition + TlsSessionTicketKeys: MessageTypeDefinition + } + } + } + } + service: { + status: { + v3: { + ClientConfig: MessageTypeDefinition + ClientConfigStatus: EnumTypeDefinition + /** + * CSDS is Client Status Discovery Service. It can be used to get the status of + * an xDS-compliant client from the management server's point of view. It can + * also be used to get the current xDS states directly from the client. + */ + ClientStatusDiscoveryService: SubtypeConstructor & { service: _envoy_service_status_v3_ClientStatusDiscoveryServiceDefinition } + ClientStatusRequest: MessageTypeDefinition + ClientStatusResponse: MessageTypeDefinition + ConfigStatus: EnumTypeDefinition + PerXdsConfig: MessageTypeDefinition + } + } + } + type: { + matcher: { + v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition + ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition + NodeMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + StructMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition + } + } + metadata: { + v3: { + MetadataKey: MessageTypeDefinition + MetadataKind: MessageTypeDefinition + } + } + tracing: { + v3: { + CustomTag: MessageTypeDefinition + } + } + v3: { + CodecClientType: EnumTypeDefinition + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + api: { + CustomHttpPattern: MessageTypeDefinition + Http: MessageTypeDefinition + HttpRule: MessageTypeDefinition + } + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + Empty: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FieldSecurityAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + CollectionEntry: MessageTypeDefinition + ContextParams: MessageTypeDefinition + ResourceLocator: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/endpoint.ts b/packages/grpc-js-xds/src/generated/endpoint.ts new file mode 100644 index 000000000..9a87bc9a5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/endpoint.ts @@ -0,0 +1,187 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + annotations: { + } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + EventServiceConfig: MessageTypeDefinition + Extension: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HealthCheck: MessageTypeDefinition + HealthStatus: EnumTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + endpoint: { + v3: { + ClusterLoadAssignment: MessageTypeDefinition + Endpoint: MessageTypeDefinition + LbEndpoint: MessageTypeDefinition + LedsClusterLocalityConfig: MessageTypeDefinition + LocalityLbEndpoints: MessageTypeDefinition + } + } + } + type: { + matcher: { + v3: { + ListStringMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } + } + v3: { + CodecClientType: EnumTypeDefinition + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + Empty: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + ContextParams: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts new file mode 100644 index 000000000..d47f00ef3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts @@ -0,0 +1,32 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Bootstrap as _envoy_config_bootstrap_v3_Bootstrap, Bootstrap__Output as _envoy_config_bootstrap_v3_Bootstrap__Output } from '../../../envoy/config/bootstrap/v3/Bootstrap'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; + +/** + * This message describes the bootstrap configuration that Envoy was started with. This includes + * any CLI overrides that were merged. Bootstrap configuration information can be used to recreate + * the static portions of an Envoy configuration by reusing the output as the bootstrap + * configuration for another Envoy. + */ +export interface BootstrapConfigDump { + 'bootstrap'?: (_envoy_config_bootstrap_v3_Bootstrap | null); + /** + * The timestamp when the BootstrapConfig was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +/** + * This message describes the bootstrap configuration that Envoy was started with. This includes + * any CLI overrides that were merged. Bootstrap configuration information can be used to recreate + * the static portions of an Envoy configuration by reusing the output as the bootstrap + * configuration for another Envoy. + */ +export interface BootstrapConfigDump__Output { + 'bootstrap': (_envoy_config_bootstrap_v3_Bootstrap__Output | null); + /** + * The timestamp when the BootstrapConfig was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts new file mode 100644 index 000000000..31c3a813a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +/** + * Resource status from the view of a xDS client, which tells the synchronization + * status between the xDS client and the xDS server. + */ +export enum ClientResourceStatus { + /** + * Resource status is not available/unknown. + */ + UNKNOWN = 0, + /** + * Client requested this resource but hasn't received any update from management + * server. The client will not fail requests, but will queue them until update + * arrives or the client times out waiting for the resource. + */ + REQUESTED = 1, + /** + * This resource has been requested by the client but has either not been + * delivered by the server or was previously delivered by the server and then + * subsequently removed from resources provided by the server. For more + * information, please refer to the :ref:`"Knowing When a Requested Resource + * Does Not Exist" ` section. + */ + DOES_NOT_EXIST = 2, + /** + * Client received this resource and replied with ACK. + */ + ACKED = 3, + /** + * Client received this resource and replied with NACK. + */ + NACKED = 4, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts new file mode 100644 index 000000000..ab7c528bf --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts @@ -0,0 +1,164 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * Describes a dynamically loaded cluster via the CDS API. + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_ClustersConfigDump_DynamicCluster { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time + * that the cluster was loaded. In the future, discrete per-cluster versions may be supported by + * the API. + */ + 'version_info'?: (string); + /** + * The cluster config. + */ + 'cluster'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Cluster was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * Describes a dynamically loaded cluster via the CDS API. + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_ClustersConfigDump_DynamicCluster__Output { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time + * that the cluster was loaded. In the future, discrete per-cluster versions may be supported by + * the API. + */ + 'version_info': (string); + /** + * The cluster config. + */ + 'cluster': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Cluster was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * Describes a statically loaded cluster. + */ +export interface _envoy_admin_v3_ClustersConfigDump_StaticCluster { + /** + * The cluster config. + */ + 'cluster'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Cluster was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +/** + * Describes a statically loaded cluster. + */ +export interface _envoy_admin_v3_ClustersConfigDump_StaticCluster__Output { + /** + * The cluster config. + */ + 'cluster': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Cluster was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's cluster manager fills this message with all currently known clusters. Cluster + * configuration information can be used to recreate an Envoy configuration by populating all + * clusters as static clusters or by returning them in a CDS response. + */ +export interface ClustersConfigDump { + /** + * This is the :ref:`version_info ` in the + * last processed CDS discovery response. If there are only static bootstrap clusters, this field + * will be "". + */ + 'version_info'?: (string); + /** + * The statically loaded cluster configs. + */ + 'static_clusters'?: (_envoy_admin_v3_ClustersConfigDump_StaticCluster)[]; + /** + * The dynamically loaded active clusters. These are clusters that are available to service + * data plane traffic. + */ + 'dynamic_active_clusters'?: (_envoy_admin_v3_ClustersConfigDump_DynamicCluster)[]; + /** + * The dynamically loaded warming clusters. These are clusters that are currently undergoing + * warming in preparation to service data plane traffic. Note that if attempting to recreate an + * Envoy configuration from a configuration dump, the warming clusters should generally be + * discarded. + */ + 'dynamic_warming_clusters'?: (_envoy_admin_v3_ClustersConfigDump_DynamicCluster)[]; +} + +/** + * Envoy's cluster manager fills this message with all currently known clusters. Cluster + * configuration information can be used to recreate an Envoy configuration by populating all + * clusters as static clusters or by returning them in a CDS response. + */ +export interface ClustersConfigDump__Output { + /** + * This is the :ref:`version_info ` in the + * last processed CDS discovery response. If there are only static bootstrap clusters, this field + * will be "". + */ + 'version_info': (string); + /** + * The statically loaded cluster configs. + */ + 'static_clusters': (_envoy_admin_v3_ClustersConfigDump_StaticCluster__Output)[]; + /** + * The dynamically loaded active clusters. These are clusters that are available to service + * data plane traffic. + */ + 'dynamic_active_clusters': (_envoy_admin_v3_ClustersConfigDump_DynamicCluster__Output)[]; + /** + * The dynamically loaded warming clusters. These are clusters that are currently undergoing + * warming in preparation to service data plane traffic. Note that if attempting to recreate an + * Envoy configuration from a configuration dump, the warming clusters should generally be + * discarded. + */ + 'dynamic_warming_clusters': (_envoy_admin_v3_ClustersConfigDump_DynamicCluster__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts new file mode 100644 index 000000000..8a0ab65c2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts @@ -0,0 +1,65 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * The :ref:`/config_dump ` admin endpoint uses this wrapper + * message to maintain and serve arbitrary configuration information from any component in Envoy. + */ +export interface ConfigDump { + /** + * This list is serialized and dumped in its entirety at the + * :ref:`/config_dump ` endpoint. + * + * The following configurations are currently supported and will be dumped in the order given + * below: + * + * * *bootstrap*: :ref:`BootstrapConfigDump ` + * * *clusters*: :ref:`ClustersConfigDump ` + * * *endpoints*: :ref:`EndpointsConfigDump ` + * * *listeners*: :ref:`ListenersConfigDump ` + * * *scoped_routes*: :ref:`ScopedRoutesConfigDump ` + * * *routes*: :ref:`RoutesConfigDump ` + * * *secrets*: :ref:`SecretsConfigDump ` + * + * EDS Configuration will only be dumped by using parameter `?include_eds` + * + * You can filter output with the resource and mask query parameters. + * See :ref:`/config_dump?resource={} `, + * :ref:`/config_dump?mask={} `, + * or :ref:`/config_dump?resource={},mask={} + * ` for more information. + */ + 'configs'?: (_google_protobuf_Any)[]; +} + +/** + * The :ref:`/config_dump ` admin endpoint uses this wrapper + * message to maintain and serve arbitrary configuration information from any component in Envoy. + */ +export interface ConfigDump__Output { + /** + * This list is serialized and dumped in its entirety at the + * :ref:`/config_dump ` endpoint. + * + * The following configurations are currently supported and will be dumped in the order given + * below: + * + * * *bootstrap*: :ref:`BootstrapConfigDump ` + * * *clusters*: :ref:`ClustersConfigDump ` + * * *endpoints*: :ref:`EndpointsConfigDump ` + * * *listeners*: :ref:`ListenersConfigDump ` + * * *scoped_routes*: :ref:`ScopedRoutesConfigDump ` + * * *routes*: :ref:`RoutesConfigDump ` + * * *secrets*: :ref:`SecretsConfigDump ` + * + * EDS Configuration will only be dumped by using parameter `?include_eds` + * + * You can filter output with the resource and mask query parameters. + * See :ref:`/config_dump?resource={} `, + * :ref:`/config_dump?mask={} `, + * or :ref:`/config_dump?resource={},mask={} + * ` for more information. + */ + 'configs': (_google_protobuf_Any__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts new file mode 100644 index 000000000..d68b27e7c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts @@ -0,0 +1,126 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig { + /** + * [#not-implemented-hide:] This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the endpoint configuration was loaded. + */ + 'version_info'?: (string); + /** + * The endpoint config. + */ + 'endpoint_config'?: (_google_protobuf_Any | null); + /** + * [#not-implemented-hide:] The timestamp when the Endpoint was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig__Output { + /** + * [#not-implemented-hide:] This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the endpoint configuration was loaded. + */ + 'version_info': (string); + /** + * The endpoint config. + */ + 'endpoint_config': (_google_protobuf_Any__Output | null); + /** + * [#not-implemented-hide:] The timestamp when the Endpoint was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +export interface _envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig { + /** + * The endpoint config. + */ + 'endpoint_config'?: (_google_protobuf_Any | null); + /** + * [#not-implemented-hide:] The timestamp when the Endpoint was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +export interface _envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig__Output { + /** + * The endpoint config. + */ + 'endpoint_config': (_google_protobuf_Any__Output | null); + /** + * [#not-implemented-hide:] The timestamp when the Endpoint was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's admin fill this message with all currently known endpoints. Endpoint + * configuration information can be used to recreate an Envoy configuration by populating all + * endpoints as static endpoints or by returning them in an EDS response. + */ +export interface EndpointsConfigDump { + /** + * The statically loaded endpoint configs. + */ + 'static_endpoint_configs'?: (_envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig)[]; + /** + * The dynamically loaded endpoint configs. + */ + 'dynamic_endpoint_configs'?: (_envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig)[]; +} + +/** + * Envoy's admin fill this message with all currently known endpoints. Endpoint + * configuration information can be used to recreate an Envoy configuration by populating all + * endpoints as static endpoints or by returning them in an EDS response. + */ +export interface EndpointsConfigDump__Output { + /** + * The statically loaded endpoint configs. + */ + 'static_endpoint_configs': (_envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig__Output)[]; + /** + * The dynamically loaded endpoint configs. + */ + 'dynamic_endpoint_configs': (_envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts new file mode 100644 index 000000000..745abedae --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts @@ -0,0 +1,198 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * Describes a dynamically loaded listener via the LDS API. + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_ListenersConfigDump_DynamicListener { + /** + * The name or unique id of this listener, pulled from the DynamicListenerState config. + */ + 'name'?: (string); + /** + * The listener state for any active listener by this name. + * These are listeners that are available to service data plane traffic. + */ + 'active_state'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState | null); + /** + * The listener state for any warming listener by this name. + * These are listeners that are currently undergoing warming in preparation to service data + * plane traffic. Note that if attempting to recreate an Envoy configuration from a + * configuration dump, the warming listeners should generally be discarded. + */ + 'warming_state'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState | null); + /** + * The listener state for any draining listener by this name. + * These are listeners that are currently undergoing draining in preparation to stop servicing + * data plane traffic. Note that if attempting to recreate an Envoy configuration from a + * configuration dump, the draining listeners should generally be discarded. + */ + 'draining_state'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * Describes a dynamically loaded listener via the LDS API. + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_ListenersConfigDump_DynamicListener__Output { + /** + * The name or unique id of this listener, pulled from the DynamicListenerState config. + */ + 'name': (string); + /** + * The listener state for any active listener by this name. + * These are listeners that are available to service data plane traffic. + */ + 'active_state': (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output | null); + /** + * The listener state for any warming listener by this name. + * These are listeners that are currently undergoing warming in preparation to service data + * plane traffic. Note that if attempting to recreate an Envoy configuration from a + * configuration dump, the warming listeners should generally be discarded. + */ + 'warming_state': (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output | null); + /** + * The listener state for any draining listener by this name. + * These are listeners that are currently undergoing draining in preparation to stop servicing + * data plane traffic. Note that if attempting to recreate an Envoy configuration from a + * configuration dump, the draining listeners should generally be discarded. + */ + 'draining_state': (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +export interface _envoy_admin_v3_ListenersConfigDump_DynamicListenerState { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time + * that the listener was loaded. In the future, discrete per-listener versions may be supported + * by the API. + */ + 'version_info'?: (string); + /** + * The listener config. + */ + 'listener'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Listener was last successfully updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +export interface _envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time + * that the listener was loaded. In the future, discrete per-listener versions may be supported + * by the API. + */ + 'version_info': (string); + /** + * The listener config. + */ + 'listener': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Listener was last successfully updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Describes a statically loaded listener. + */ +export interface _envoy_admin_v3_ListenersConfigDump_StaticListener { + /** + * The listener config. + */ + 'listener'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Listener was last successfully updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +/** + * Describes a statically loaded listener. + */ +export interface _envoy_admin_v3_ListenersConfigDump_StaticListener__Output { + /** + * The listener config. + */ + 'listener': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Listener was last successfully updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's listener manager fills this message with all currently known listeners. Listener + * configuration information can be used to recreate an Envoy configuration by populating all + * listeners as static listeners or by returning them in a LDS response. + */ +export interface ListenersConfigDump { + /** + * This is the :ref:`version_info ` in the + * last processed LDS discovery response. If there are only static bootstrap listeners, this field + * will be "". + */ + 'version_info'?: (string); + /** + * The statically loaded listener configs. + */ + 'static_listeners'?: (_envoy_admin_v3_ListenersConfigDump_StaticListener)[]; + /** + * State for any warming, active, or draining listeners. + */ + 'dynamic_listeners'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListener)[]; +} + +/** + * Envoy's listener manager fills this message with all currently known listeners. Listener + * configuration information can be used to recreate an Envoy configuration by populating all + * listeners as static listeners or by returning them in a LDS response. + */ +export interface ListenersConfigDump__Output { + /** + * This is the :ref:`version_info ` in the + * last processed LDS discovery response. If there are only static bootstrap listeners, this field + * will be "". + */ + 'version_info': (string); + /** + * The statically loaded listener configs. + */ + 'static_listeners': (_envoy_admin_v3_ListenersConfigDump_StaticListener__Output)[]; + /** + * State for any warming, active, or draining listeners. + */ + 'dynamic_listeners': (_envoy_admin_v3_ListenersConfigDump_DynamicListener__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts new file mode 100644 index 000000000..2a62e9b74 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts @@ -0,0 +1,130 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the route configuration was loaded. + */ + 'version_info'?: (string); + /** + * The route config. + */ + 'route_config'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Route was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig__Output { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the route configuration was loaded. + */ + 'version_info': (string); + /** + * The route config. + */ + 'route_config': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Route was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +export interface _envoy_admin_v3_RoutesConfigDump_StaticRouteConfig { + /** + * The route config. + */ + 'route_config'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Route was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +export interface _envoy_admin_v3_RoutesConfigDump_StaticRouteConfig__Output { + /** + * The route config. + */ + 'route_config': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Route was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's RDS implementation fills this message with all currently loaded routes, as described by + * their RouteConfiguration objects. Static routes that are either defined in the bootstrap configuration + * or defined inline while configuring listeners are separated from those configured dynamically via RDS. + * Route configuration information can be used to recreate an Envoy configuration by populating all routes + * as static routes or by returning them in RDS responses. + */ +export interface RoutesConfigDump { + /** + * The statically loaded route configs. + */ + 'static_route_configs'?: (_envoy_admin_v3_RoutesConfigDump_StaticRouteConfig)[]; + /** + * The dynamically loaded route configs. + */ + 'dynamic_route_configs'?: (_envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig)[]; +} + +/** + * Envoy's RDS implementation fills this message with all currently loaded routes, as described by + * their RouteConfiguration objects. Static routes that are either defined in the bootstrap configuration + * or defined inline while configuring listeners are separated from those configured dynamically via RDS. + * Route configuration information can be used to recreate an Envoy configuration by populating all routes + * as static routes or by returning them in RDS responses. + */ +export interface RoutesConfigDump__Output { + /** + * The statically loaded route configs. + */ + 'static_route_configs': (_envoy_admin_v3_RoutesConfigDump_StaticRouteConfig__Output)[]; + /** + * The dynamically loaded route configs. + */ + 'dynamic_route_configs': (_envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts new file mode 100644 index 000000000..f271635bc --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts @@ -0,0 +1,144 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfigs { + /** + * The name assigned to the scoped route configurations. + */ + 'name'?: (string); + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the scoped routes configuration was loaded. + */ + 'version_info'?: (string); + /** + * The scoped route configurations. + */ + 'scoped_route_configs'?: (_google_protobuf_Any)[]; + /** + * The timestamp when the scoped route config set was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfigs__Output { + /** + * The name assigned to the scoped route configurations. + */ + 'name': (string); + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the scoped routes configuration was loaded. + */ + 'version_info': (string); + /** + * The scoped route configurations. + */ + 'scoped_route_configs': (_google_protobuf_Any__Output)[]; + /** + * The timestamp when the scoped route config set was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +export interface _envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs { + /** + * The name assigned to the scoped route configurations. + */ + 'name'?: (string); + /** + * The scoped route configurations. + */ + 'scoped_route_configs'?: (_google_protobuf_Any)[]; + /** + * The timestamp when the scoped route config set was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +export interface _envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs__Output { + /** + * The name assigned to the scoped route configurations. + */ + 'name': (string); + /** + * The scoped route configurations. + */ + 'scoped_route_configs': (_google_protobuf_Any__Output)[]; + /** + * The timestamp when the scoped route config set was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's scoped RDS implementation fills this message with all currently loaded route + * configuration scopes (defined via ScopedRouteConfigurationsSet protos). This message lists both + * the scopes defined inline with the higher order object (i.e., the HttpConnectionManager) and the + * dynamically obtained scopes via the SRDS API. + */ +export interface ScopedRoutesConfigDump { + /** + * The statically loaded scoped route configs. + */ + 'inline_scoped_route_configs'?: (_envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs)[]; + /** + * The dynamically loaded scoped route configs. + */ + 'dynamic_scoped_route_configs'?: (_envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfigs)[]; +} + +/** + * Envoy's scoped RDS implementation fills this message with all currently loaded route + * configuration scopes (defined via ScopedRouteConfigurationsSet protos). This message lists both + * the scopes defined inline with the higher order object (i.e., the HttpConnectionManager) and the + * dynamically obtained scopes via the SRDS API. + */ +export interface ScopedRoutesConfigDump__Output { + /** + * The statically loaded scoped route configs. + */ + 'inline_scoped_route_configs': (_envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs__Output)[]; + /** + * The dynamically loaded scoped route configs. + */ + 'dynamic_scoped_route_configs': (_envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfigs__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts new file mode 100644 index 000000000..21921edec --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts @@ -0,0 +1,162 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * DynamicSecret contains secret information fetched via SDS. + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_SecretsConfigDump_DynamicSecret { + /** + * The name assigned to the secret. + */ + 'name'?: (string); + /** + * This is the per-resource version information. + */ + 'version_info'?: (string); + /** + * The timestamp when the secret was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * The actual secret information. + * Security sensitive information is redacted (replaced with "[redacted]") for + * private keys and passwords in TLS certificates. + */ + 'secret'?: (_google_protobuf_Any | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * DynamicSecret contains secret information fetched via SDS. + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output { + /** + * The name assigned to the secret. + */ + 'name': (string); + /** + * This is the per-resource version information. + */ + 'version_info': (string); + /** + * The timestamp when the secret was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * The actual secret information. + * Security sensitive information is redacted (replaced with "[redacted]") for + * private keys and passwords in TLS certificates. + */ + 'secret': (_google_protobuf_Any__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * StaticSecret specifies statically loaded secret in bootstrap. + */ +export interface _envoy_admin_v3_SecretsConfigDump_StaticSecret { + /** + * The name assigned to the secret. + */ + 'name'?: (string); + /** + * The timestamp when the secret was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * The actual secret information. + * Security sensitive information is redacted (replaced with "[redacted]") for + * private keys and passwords in TLS certificates. + */ + 'secret'?: (_google_protobuf_Any | null); +} + +/** + * StaticSecret specifies statically loaded secret in bootstrap. + */ +export interface _envoy_admin_v3_SecretsConfigDump_StaticSecret__Output { + /** + * The name assigned to the secret. + */ + 'name': (string); + /** + * The timestamp when the secret was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * The actual secret information. + * Security sensitive information is redacted (replaced with "[redacted]") for + * private keys and passwords in TLS certificates. + */ + 'secret': (_google_protobuf_Any__Output | null); +} + +/** + * Envoys SDS implementation fills this message with all secrets fetched dynamically via SDS. + */ +export interface SecretsConfigDump { + /** + * The statically loaded secrets. + */ + 'static_secrets'?: (_envoy_admin_v3_SecretsConfigDump_StaticSecret)[]; + /** + * The dynamically loaded active secrets. These are secrets that are available to service + * clusters or listeners. + */ + 'dynamic_active_secrets'?: (_envoy_admin_v3_SecretsConfigDump_DynamicSecret)[]; + /** + * The dynamically loaded warming secrets. These are secrets that are currently undergoing + * warming in preparation to service clusters or listeners. + */ + 'dynamic_warming_secrets'?: (_envoy_admin_v3_SecretsConfigDump_DynamicSecret)[]; +} + +/** + * Envoys SDS implementation fills this message with all secrets fetched dynamically via SDS. + */ +export interface SecretsConfigDump__Output { + /** + * The statically loaded secrets. + */ + 'static_secrets': (_envoy_admin_v3_SecretsConfigDump_StaticSecret__Output)[]; + /** + * The dynamically loaded active secrets. These are secrets that are available to service + * clusters or listeners. + */ + 'dynamic_active_secrets': (_envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output)[]; + /** + * The dynamically loaded warming secrets. These are secrets that are currently undergoing + * warming in preparation to service clusters or listeners. + */ + 'dynamic_warming_secrets': (_envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts new file mode 100644 index 000000000..b98e8cd4d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts @@ -0,0 +1,46 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; + +export interface UpdateFailureState { + /** + * What the component configuration would have been if the update had succeeded. + * This field may not be populated by xDS clients due to storage overhead. + */ + 'failed_configuration'?: (_google_protobuf_Any | null); + /** + * Time of the latest failed update attempt. + */ + 'last_update_attempt'?: (_google_protobuf_Timestamp | null); + /** + * Details about the last failed update attempt. + */ + 'details'?: (string); + /** + * This is the version of the rejected resource. + * [#not-implemented-hide:] + */ + 'version_info'?: (string); +} + +export interface UpdateFailureState__Output { + /** + * What the component configuration would have been if the update had succeeded. + * This field may not be populated by xDS clients due to storage overhead. + */ + 'failed_configuration': (_google_protobuf_Any__Output | null); + /** + * Time of the latest failed update attempt. + */ + 'last_update_attempt': (_google_protobuf_Timestamp__Output | null); + /** + * Details about the last failed update attempt. + */ + 'details': (string); + /** + * This is the version of the rejected resource. + * [#not-implemented-hide:] + */ + 'version_info': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts new file mode 100644 index 000000000..367d8f302 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts @@ -0,0 +1,42 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { AccessLogFilter as _envoy_config_accesslog_v3_AccessLogFilter, AccessLogFilter__Output as _envoy_config_accesslog_v3_AccessLogFilter__Output } from '../../../../envoy/config/accesslog/v3/AccessLogFilter'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +export interface AccessLog { + /** + * The name of the access log extension to instantiate. + * The name must match one of the compiled in loggers. + * See the :ref:`extensions listed in typed_config below ` for the default list of available loggers. + */ + 'name'?: (string); + /** + * Filter which is used to determine if the access log needs to be written. + */ + 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter | null); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Custom configuration that must be set according to the access logger extension being instantiated. + * [#extension-category: envoy.access_loggers] + */ + 'config_type'?: "typed_config"; +} + +export interface AccessLog__Output { + /** + * The name of the access log extension to instantiate. + * The name must match one of the compiled in loggers. + * See the :ref:`extensions listed in typed_config below ` for the default list of available loggers. + */ + 'name': (string); + /** + * Filter which is used to determine if the access log needs to be written. + */ + 'filter': (_envoy_config_accesslog_v3_AccessLogFilter__Output | null); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Custom configuration that must be set according to the access logger extension being instantiated. + * [#extension-category: envoy.access_loggers] + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts new file mode 100644 index 000000000..85a952c9c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts @@ -0,0 +1,124 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { StatusCodeFilter as _envoy_config_accesslog_v3_StatusCodeFilter, StatusCodeFilter__Output as _envoy_config_accesslog_v3_StatusCodeFilter__Output } from '../../../../envoy/config/accesslog/v3/StatusCodeFilter'; +import type { DurationFilter as _envoy_config_accesslog_v3_DurationFilter, DurationFilter__Output as _envoy_config_accesslog_v3_DurationFilter__Output } from '../../../../envoy/config/accesslog/v3/DurationFilter'; +import type { NotHealthCheckFilter as _envoy_config_accesslog_v3_NotHealthCheckFilter, NotHealthCheckFilter__Output as _envoy_config_accesslog_v3_NotHealthCheckFilter__Output } from '../../../../envoy/config/accesslog/v3/NotHealthCheckFilter'; +import type { TraceableFilter as _envoy_config_accesslog_v3_TraceableFilter, TraceableFilter__Output as _envoy_config_accesslog_v3_TraceableFilter__Output } from '../../../../envoy/config/accesslog/v3/TraceableFilter'; +import type { RuntimeFilter as _envoy_config_accesslog_v3_RuntimeFilter, RuntimeFilter__Output as _envoy_config_accesslog_v3_RuntimeFilter__Output } from '../../../../envoy/config/accesslog/v3/RuntimeFilter'; +import type { AndFilter as _envoy_config_accesslog_v3_AndFilter, AndFilter__Output as _envoy_config_accesslog_v3_AndFilter__Output } from '../../../../envoy/config/accesslog/v3/AndFilter'; +import type { OrFilter as _envoy_config_accesslog_v3_OrFilter, OrFilter__Output as _envoy_config_accesslog_v3_OrFilter__Output } from '../../../../envoy/config/accesslog/v3/OrFilter'; +import type { HeaderFilter as _envoy_config_accesslog_v3_HeaderFilter, HeaderFilter__Output as _envoy_config_accesslog_v3_HeaderFilter__Output } from '../../../../envoy/config/accesslog/v3/HeaderFilter'; +import type { ResponseFlagFilter as _envoy_config_accesslog_v3_ResponseFlagFilter, ResponseFlagFilter__Output as _envoy_config_accesslog_v3_ResponseFlagFilter__Output } from '../../../../envoy/config/accesslog/v3/ResponseFlagFilter'; +import type { GrpcStatusFilter as _envoy_config_accesslog_v3_GrpcStatusFilter, GrpcStatusFilter__Output as _envoy_config_accesslog_v3_GrpcStatusFilter__Output } from '../../../../envoy/config/accesslog/v3/GrpcStatusFilter'; +import type { ExtensionFilter as _envoy_config_accesslog_v3_ExtensionFilter, ExtensionFilter__Output as _envoy_config_accesslog_v3_ExtensionFilter__Output } from '../../../../envoy/config/accesslog/v3/ExtensionFilter'; +import type { MetadataFilter as _envoy_config_accesslog_v3_MetadataFilter, MetadataFilter__Output as _envoy_config_accesslog_v3_MetadataFilter__Output } from '../../../../envoy/config/accesslog/v3/MetadataFilter'; + +/** + * [#next-free-field: 13] + */ +export interface AccessLogFilter { + /** + * Status code filter. + */ + 'status_code_filter'?: (_envoy_config_accesslog_v3_StatusCodeFilter | null); + /** + * Duration filter. + */ + 'duration_filter'?: (_envoy_config_accesslog_v3_DurationFilter | null); + /** + * Not health check filter. + */ + 'not_health_check_filter'?: (_envoy_config_accesslog_v3_NotHealthCheckFilter | null); + /** + * Traceable filter. + */ + 'traceable_filter'?: (_envoy_config_accesslog_v3_TraceableFilter | null); + /** + * Runtime filter. + */ + 'runtime_filter'?: (_envoy_config_accesslog_v3_RuntimeFilter | null); + /** + * And filter. + */ + 'and_filter'?: (_envoy_config_accesslog_v3_AndFilter | null); + /** + * Or filter. + */ + 'or_filter'?: (_envoy_config_accesslog_v3_OrFilter | null); + /** + * Header filter. + */ + 'header_filter'?: (_envoy_config_accesslog_v3_HeaderFilter | null); + /** + * Response flag filter. + */ + 'response_flag_filter'?: (_envoy_config_accesslog_v3_ResponseFlagFilter | null); + /** + * gRPC status filter. + */ + 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter | null); + /** + * Extension filter. + */ + 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter | null); + /** + * Metadata Filter + */ + 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter | null); + 'filter_specifier'?: "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"; +} + +/** + * [#next-free-field: 13] + */ +export interface AccessLogFilter__Output { + /** + * Status code filter. + */ + 'status_code_filter'?: (_envoy_config_accesslog_v3_StatusCodeFilter__Output | null); + /** + * Duration filter. + */ + 'duration_filter'?: (_envoy_config_accesslog_v3_DurationFilter__Output | null); + /** + * Not health check filter. + */ + 'not_health_check_filter'?: (_envoy_config_accesslog_v3_NotHealthCheckFilter__Output | null); + /** + * Traceable filter. + */ + 'traceable_filter'?: (_envoy_config_accesslog_v3_TraceableFilter__Output | null); + /** + * Runtime filter. + */ + 'runtime_filter'?: (_envoy_config_accesslog_v3_RuntimeFilter__Output | null); + /** + * And filter. + */ + 'and_filter'?: (_envoy_config_accesslog_v3_AndFilter__Output | null); + /** + * Or filter. + */ + 'or_filter'?: (_envoy_config_accesslog_v3_OrFilter__Output | null); + /** + * Header filter. + */ + 'header_filter'?: (_envoy_config_accesslog_v3_HeaderFilter__Output | null); + /** + * Response flag filter. + */ + 'response_flag_filter'?: (_envoy_config_accesslog_v3_ResponseFlagFilter__Output | null); + /** + * gRPC status filter. + */ + 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter__Output | null); + /** + * Extension filter. + */ + 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter__Output | null); + /** + * Metadata Filter + */ + 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter__Output | null); + 'filter_specifier': "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AndFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AndFilter.ts new file mode 100644 index 000000000..c3c2ac8b4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AndFilter.ts @@ -0,0 +1,21 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { AccessLogFilter as _envoy_config_accesslog_v3_AccessLogFilter, AccessLogFilter__Output as _envoy_config_accesslog_v3_AccessLogFilter__Output } from '../../../../envoy/config/accesslog/v3/AccessLogFilter'; + +/** + * Performs a logical “and†operation on the result of each filter in filters. + * Filters are evaluated sequentially and if one of them returns false, the + * filter returns false immediately. + */ +export interface AndFilter { + 'filters'?: (_envoy_config_accesslog_v3_AccessLogFilter)[]; +} + +/** + * Performs a logical “and†operation on the result of each filter in filters. + * Filters are evaluated sequentially and if one of them returns false, the + * filter returns false immediately. + */ +export interface AndFilter__Output { + 'filters': (_envoy_config_accesslog_v3_AccessLogFilter__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts new file mode 100644 index 000000000..07893f2dd --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts @@ -0,0 +1,48 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { RuntimeUInt32 as _envoy_config_core_v3_RuntimeUInt32, RuntimeUInt32__Output as _envoy_config_core_v3_RuntimeUInt32__Output } from '../../../../envoy/config/core/v3/RuntimeUInt32'; + +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +export enum _envoy_config_accesslog_v3_ComparisonFilter_Op { + /** + * = + */ + EQ = 0, + /** + * >= + */ + GE = 1, + /** + * <= + */ + LE = 2, +} + +/** + * Filter on an integer comparison. + */ +export interface ComparisonFilter { + /** + * Comparison operator. + */ + 'op'?: (_envoy_config_accesslog_v3_ComparisonFilter_Op | keyof typeof _envoy_config_accesslog_v3_ComparisonFilter_Op); + /** + * Value to compare against. + */ + 'value'?: (_envoy_config_core_v3_RuntimeUInt32 | null); +} + +/** + * Filter on an integer comparison. + */ +export interface ComparisonFilter__Output { + /** + * Comparison operator. + */ + 'op': (keyof typeof _envoy_config_accesslog_v3_ComparisonFilter_Op); + /** + * Value to compare against. + */ + 'value': (_envoy_config_core_v3_RuntimeUInt32__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts new file mode 100644 index 000000000..ee61e55fa --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts @@ -0,0 +1,23 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { ComparisonFilter as _envoy_config_accesslog_v3_ComparisonFilter, ComparisonFilter__Output as _envoy_config_accesslog_v3_ComparisonFilter__Output } from '../../../../envoy/config/accesslog/v3/ComparisonFilter'; + +/** + * Filters on total request duration in milliseconds. + */ +export interface DurationFilter { + /** + * Comparison. + */ + 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter | null); +} + +/** + * Filters on total request duration in milliseconds. + */ +export interface DurationFilter__Output { + /** + * Comparison. + */ + 'comparison': (_envoy_config_accesslog_v3_ComparisonFilter__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts new file mode 100644 index 000000000..19edb671b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Extension filter is statically registered at runtime. + */ +export interface ExtensionFilter { + /** + * The name of the filter implementation to instantiate. The name must + * match a statically registered filter. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Custom configuration that depends on the filter being instantiated. + */ + 'config_type'?: "typed_config"; +} + +/** + * Extension filter is statically registered at runtime. + */ +export interface ExtensionFilter__Output { + /** + * The name of the filter implementation to instantiate. The name must + * match a statically registered filter. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Custom configuration that depends on the filter being instantiated. + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts new file mode 100644 index 000000000..f7ccc8052 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts @@ -0,0 +1,58 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + + +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +export enum _envoy_config_accesslog_v3_GrpcStatusFilter_Status { + OK = 0, + CANCELED = 1, + UNKNOWN = 2, + INVALID_ARGUMENT = 3, + DEADLINE_EXCEEDED = 4, + NOT_FOUND = 5, + ALREADY_EXISTS = 6, + PERMISSION_DENIED = 7, + RESOURCE_EXHAUSTED = 8, + FAILED_PRECONDITION = 9, + ABORTED = 10, + OUT_OF_RANGE = 11, + UNIMPLEMENTED = 12, + INTERNAL = 13, + UNAVAILABLE = 14, + DATA_LOSS = 15, + UNAUTHENTICATED = 16, +} + +/** + * Filters gRPC requests based on their response status. If a gRPC status is not + * provided, the filter will infer the status from the HTTP status code. + */ +export interface GrpcStatusFilter { + /** + * Logs only responses that have any one of the gRPC statuses in this field. + */ + 'statuses'?: (_envoy_config_accesslog_v3_GrpcStatusFilter_Status | keyof typeof _envoy_config_accesslog_v3_GrpcStatusFilter_Status)[]; + /** + * If included and set to true, the filter will instead block all responses + * with a gRPC status or inferred gRPC status enumerated in statuses, and + * allow all other responses. + */ + 'exclude'?: (boolean); +} + +/** + * Filters gRPC requests based on their response status. If a gRPC status is not + * provided, the filter will infer the status from the HTTP status code. + */ +export interface GrpcStatusFilter__Output { + /** + * Logs only responses that have any one of the gRPC statuses in this field. + */ + 'statuses': (keyof typeof _envoy_config_accesslog_v3_GrpcStatusFilter_Status)[]; + /** + * If included and set to true, the filter will instead block all responses + * with a gRPC status or inferred gRPC status enumerated in statuses, and + * allow all other responses. + */ + 'exclude': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts new file mode 100644 index 000000000..294084d1c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; + +/** + * Filters requests based on the presence or value of a request header. + */ +export interface HeaderFilter { + /** + * Only requests with a header which matches the specified HeaderMatcher will + * pass the filter check. + */ + 'header'?: (_envoy_config_route_v3_HeaderMatcher | null); +} + +/** + * Filters requests based on the presence or value of a request header. + */ +export interface HeaderFilter__Output { + /** + * Only requests with a header which matches the specified HeaderMatcher will + * pass the filter check. + */ + 'header': (_envoy_config_route_v3_HeaderMatcher__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts new file mode 100644 index 000000000..cd821fef6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts @@ -0,0 +1,48 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { MetadataMatcher as _envoy_type_matcher_v3_MetadataMatcher, MetadataMatcher__Output as _envoy_type_matcher_v3_MetadataMatcher__Output } from '../../../../envoy/type/matcher/v3/MetadataMatcher'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; + +/** + * Filters based on matching dynamic metadata. + * If the matcher path and key correspond to an existing key in dynamic + * metadata, the request is logged only if the matcher value is equal to the + * metadata value. If the matcher path and key *do not* correspond to an + * existing key in dynamic metadata, the request is logged only if + * match_if_key_not_found is "true" or unset. + */ +export interface MetadataFilter { + /** + * Matcher to check metadata for specified value. For example, to match on the + * access_log_hint metadata, set the filter to "envoy.common" and the path to + * "access_log_hint", and the value to "true". + */ + 'matcher'?: (_envoy_type_matcher_v3_MetadataMatcher | null); + /** + * Default result if the key does not exist in dynamic metadata: if unset or + * true, then log; if false, then don't log. + */ + 'match_if_key_not_found'?: (_google_protobuf_BoolValue | null); +} + +/** + * Filters based on matching dynamic metadata. + * If the matcher path and key correspond to an existing key in dynamic + * metadata, the request is logged only if the matcher value is equal to the + * metadata value. If the matcher path and key *do not* correspond to an + * existing key in dynamic metadata, the request is logged only if + * match_if_key_not_found is "true" or unset. + */ +export interface MetadataFilter__Output { + /** + * Matcher to check metadata for specified value. For example, to match on the + * access_log_hint metadata, set the filter to "envoy.common" and the path to + * "access_log_hint", and the value to "true". + */ + 'matcher': (_envoy_type_matcher_v3_MetadataMatcher__Output | null); + /** + * Default result if the key does not exist in dynamic metadata: if unset or + * true, then log; if false, then don't log. + */ + 'match_if_key_not_found': (_google_protobuf_BoolValue__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/NotHealthCheckFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/NotHealthCheckFilter.ts new file mode 100644 index 000000000..40728fc34 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/NotHealthCheckFilter.ts @@ -0,0 +1,16 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + + +/** + * Filters for requests that are not health check requests. A health check + * request is marked by the health check filter. + */ +export interface NotHealthCheckFilter { +} + +/** + * Filters for requests that are not health check requests. A health check + * request is marked by the health check filter. + */ +export interface NotHealthCheckFilter__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/OrFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/OrFilter.ts new file mode 100644 index 000000000..1756d3ad5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/OrFilter.ts @@ -0,0 +1,21 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { AccessLogFilter as _envoy_config_accesslog_v3_AccessLogFilter, AccessLogFilter__Output as _envoy_config_accesslog_v3_AccessLogFilter__Output } from '../../../../envoy/config/accesslog/v3/AccessLogFilter'; + +/** + * Performs a logical “or†operation on the result of each individual filter. + * Filters are evaluated sequentially and if one of them returns true, the + * filter returns true immediately. + */ +export interface OrFilter { + 'filters'?: (_envoy_config_accesslog_v3_AccessLogFilter)[]; +} + +/** + * Performs a logical “or†operation on the result of each individual filter. + * Filters are evaluated sequentially and if one of them returns true, the + * filter returns true immediately. + */ +export interface OrFilter__Output { + 'filters': (_envoy_config_accesslog_v3_AccessLogFilter__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ResponseFlagFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ResponseFlagFilter.ts new file mode 100644 index 000000000..c45a63054 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ResponseFlagFilter.ts @@ -0,0 +1,32 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + + +/** + * Filters requests that received responses with an Envoy response flag set. + * A list of the response flags can be found + * in the access log formatter + * :ref:`documentation`. + */ +export interface ResponseFlagFilter { + /** + * Only responses with the any of the flags listed in this field will be + * logged. This field is optional. If it is not specified, then any response + * flag will pass the filter check. + */ + 'flags'?: (string)[]; +} + +/** + * Filters requests that received responses with an Envoy response flag set. + * A list of the response flags can be found + * in the access log formatter + * :ref:`documentation`. + */ +export interface ResponseFlagFilter__Output { + /** + * Only responses with the any of the flags listed in this field will be + * logged. This field is optional. If it is not specified, then any response + * flag will pass the filter check. + */ + 'flags': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts new file mode 100644 index 000000000..83b075388 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts @@ -0,0 +1,73 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; + +/** + * Filters for random sampling of requests. + */ +export interface RuntimeFilter { + /** + * Runtime key to get an optional overridden numerator for use in the + * *percent_sampled* field. If found in runtime, this value will replace the + * default numerator. + */ + 'runtime_key'?: (string); + /** + * The default sampling percentage. If not specified, defaults to 0% with + * denominator of 100. + */ + 'percent_sampled'?: (_envoy_type_v3_FractionalPercent | null); + /** + * By default, sampling pivots on the header + * :ref:`x-request-id` being + * present. If :ref:`x-request-id` + * is present, the filter will consistently sample across multiple hosts based + * on the runtime key value and the value extracted from + * :ref:`x-request-id`. If it is + * missing, or *use_independent_randomness* is set to true, the filter will + * randomly sample based on the runtime key value alone. + * *use_independent_randomness* can be used for logging kill switches within + * complex nested :ref:`AndFilter + * ` and :ref:`OrFilter + * ` blocks that are easier to + * reason about from a probability perspective (i.e., setting to true will + * cause the filter to behave like an independent random variable when + * composed within logical operator filters). + */ + 'use_independent_randomness'?: (boolean); +} + +/** + * Filters for random sampling of requests. + */ +export interface RuntimeFilter__Output { + /** + * Runtime key to get an optional overridden numerator for use in the + * *percent_sampled* field. If found in runtime, this value will replace the + * default numerator. + */ + 'runtime_key': (string); + /** + * The default sampling percentage. If not specified, defaults to 0% with + * denominator of 100. + */ + 'percent_sampled': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * By default, sampling pivots on the header + * :ref:`x-request-id` being + * present. If :ref:`x-request-id` + * is present, the filter will consistently sample across multiple hosts based + * on the runtime key value and the value extracted from + * :ref:`x-request-id`. If it is + * missing, or *use_independent_randomness* is set to true, the filter will + * randomly sample based on the runtime key value alone. + * *use_independent_randomness* can be used for logging kill switches within + * complex nested :ref:`AndFilter + * ` and :ref:`OrFilter + * ` blocks that are easier to + * reason about from a probability perspective (i.e., setting to true will + * cause the filter to behave like an independent random variable when + * composed within logical operator filters). + */ + 'use_independent_randomness': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts new file mode 100644 index 000000000..a071b5cbb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts @@ -0,0 +1,23 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { ComparisonFilter as _envoy_config_accesslog_v3_ComparisonFilter, ComparisonFilter__Output as _envoy_config_accesslog_v3_ComparisonFilter__Output } from '../../../../envoy/config/accesslog/v3/ComparisonFilter'; + +/** + * Filters on HTTP response/status code. + */ +export interface StatusCodeFilter { + /** + * Comparison. + */ + 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter | null); +} + +/** + * Filters on HTTP response/status code. + */ +export interface StatusCodeFilter__Output { + /** + * Comparison. + */ + 'comparison': (_envoy_config_accesslog_v3_ComparisonFilter__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/TraceableFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/TraceableFilter.ts new file mode 100644 index 000000000..c2b3a646b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/TraceableFilter.ts @@ -0,0 +1,16 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + + +/** + * Filters for requests that are traceable. See the tracing overview for more + * information on how a request becomes traceable. + */ +export interface TraceableFilter { +} + +/** + * Filters for requests that are traceable. See the tracing overview for more + * information on how a request becomes traceable. + */ +export interface TraceableFilter__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts new file mode 100644 index 000000000..a7f3826a1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts @@ -0,0 +1,75 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; +import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../envoy/config/accesslog/v3/AccessLog'; + +/** + * Administration interface :ref:`operations documentation + * `. + * [#next-free-field: 6] + */ +export interface Admin { + /** + * The path to write the access log for the administration server. If no + * access log is desired specify ‘/dev/null’. This is only required if + * :ref:`address ` is set. + * Deprecated in favor of *access_log* which offers more options. + */ + 'access_log_path'?: (string); + /** + * The cpu profiler output path for the administration server. If no profile + * path is specified, the default is ‘/var/log/envoy/envoy.prof’. + */ + 'profile_path'?: (string); + /** + * The TCP address that the administration server will listen on. + * If not specified, Envoy will not start an administration server. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; + /** + * Configuration for :ref:`access logs ` + * emitted by the administration server. + */ + 'access_log'?: (_envoy_config_accesslog_v3_AccessLog)[]; +} + +/** + * Administration interface :ref:`operations documentation + * `. + * [#next-free-field: 6] + */ +export interface Admin__Output { + /** + * The path to write the access log for the administration server. If no + * access log is desired specify ‘/dev/null’. This is only required if + * :ref:`address ` is set. + * Deprecated in favor of *access_log* which offers more options. + */ + 'access_log_path': (string); + /** + * The cpu profiler output path for the administration server. If no profile + * path is specified, the default is ‘/var/log/envoy/envoy.prof’. + */ + 'profile_path': (string); + /** + * The TCP address that the administration server will listen on. + * If not specified, Envoy will not start an administration server. + */ + 'address': (_envoy_config_core_v3_Address__Output | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; + /** + * Configuration for :ref:`access logs ` + * emitted by the administration server. + */ + 'access_log': (_envoy_config_accesslog_v3_AccessLog__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts new file mode 100644 index 000000000..797148675 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts @@ -0,0 +1,642 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { ClusterManager as _envoy_config_bootstrap_v3_ClusterManager, ClusterManager__Output as _envoy_config_bootstrap_v3_ClusterManager__Output } from '../../../../envoy/config/bootstrap/v3/ClusterManager'; +import type { StatsSink as _envoy_config_metrics_v3_StatsSink, StatsSink__Output as _envoy_config_metrics_v3_StatsSink__Output } from '../../../../envoy/config/metrics/v3/StatsSink'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Watchdog as _envoy_config_bootstrap_v3_Watchdog, Watchdog__Output as _envoy_config_bootstrap_v3_Watchdog__Output } from '../../../../envoy/config/bootstrap/v3/Watchdog'; +import type { Tracing as _envoy_config_trace_v3_Tracing, Tracing__Output as _envoy_config_trace_v3_Tracing__Output } from '../../../../envoy/config/trace/v3/Tracing'; +import type { Admin as _envoy_config_bootstrap_v3_Admin, Admin__Output as _envoy_config_bootstrap_v3_Admin__Output } from '../../../../envoy/config/bootstrap/v3/Admin'; +import type { StatsConfig as _envoy_config_metrics_v3_StatsConfig, StatsConfig__Output as _envoy_config_metrics_v3_StatsConfig__Output } from '../../../../envoy/config/metrics/v3/StatsConfig'; +import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfigSource__Output as _envoy_config_core_v3_ApiConfigSource__Output } from '../../../../envoy/config/core/v3/ApiConfigSource'; +import type { OverloadManager as _envoy_config_overload_v3_OverloadManager, OverloadManager__Output as _envoy_config_overload_v3_OverloadManager__Output } from '../../../../envoy/config/overload/v3/OverloadManager'; +import type { LayeredRuntime as _envoy_config_bootstrap_v3_LayeredRuntime, LayeredRuntime__Output as _envoy_config_bootstrap_v3_LayeredRuntime__Output } from '../../../../envoy/config/bootstrap/v3/LayeredRuntime'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; +import type { Watchdogs as _envoy_config_bootstrap_v3_Watchdogs, Watchdogs__Output as _envoy_config_bootstrap_v3_Watchdogs__Output } from '../../../../envoy/config/bootstrap/v3/Watchdogs'; +import type { FatalAction as _envoy_config_bootstrap_v3_FatalAction, FatalAction__Output as _envoy_config_bootstrap_v3_FatalAction__Output } from '../../../../envoy/config/bootstrap/v3/FatalAction'; +import type { DnsResolutionConfig as _envoy_config_core_v3_DnsResolutionConfig, DnsResolutionConfig__Output as _envoy_config_core_v3_DnsResolutionConfig__Output } from '../../../../envoy/config/core/v3/DnsResolutionConfig'; +import type { CustomInlineHeader as _envoy_config_bootstrap_v3_CustomInlineHeader, CustomInlineHeader__Output as _envoy_config_bootstrap_v3_CustomInlineHeader__Output } from '../../../../envoy/config/bootstrap/v3/CustomInlineHeader'; +import type { Listener as _envoy_config_listener_v3_Listener, Listener__Output as _envoy_config_listener_v3_Listener__Output } from '../../../../envoy/config/listener/v3/Listener'; +import type { Cluster as _envoy_config_cluster_v3_Cluster, Cluster__Output as _envoy_config_cluster_v3_Cluster__Output } from '../../../../envoy/config/cluster/v3/Cluster'; +import type { Secret as _envoy_extensions_transport_sockets_tls_v3_Secret, Secret__Output as _envoy_extensions_transport_sockets_tls_v3_Secret__Output } from '../../../../envoy/extensions/transport_sockets/tls/v3/Secret'; +import type { Long } from '@grpc/proto-loader'; + +/** + * [#next-free-field: 7] + */ +export interface _envoy_config_bootstrap_v3_Bootstrap_DynamicResources { + /** + * All :ref:`Listeners ` are provided by a single + * :ref:`LDS ` configuration source. + */ + 'lds_config'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * xdstp:// resource locator for listener collection. + * [#not-implemented-hide:] + */ + 'lds_resources_locator'?: (string); + /** + * All post-bootstrap :ref:`Cluster ` definitions are + * provided by a single :ref:`CDS ` + * configuration source. + */ + 'cds_config'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * xdstp:// resource locator for cluster collection. + * [#not-implemented-hide:] + */ + 'cds_resources_locator'?: (string); + /** + * A single :ref:`ADS ` source may be optionally + * specified. This must have :ref:`api_type + * ` :ref:`GRPC + * `. Only + * :ref:`ConfigSources ` that have + * the :ref:`ads ` field set will be + * streamed on the ADS channel. + */ + 'ads_config'?: (_envoy_config_core_v3_ApiConfigSource | null); +} + +/** + * [#next-free-field: 7] + */ +export interface _envoy_config_bootstrap_v3_Bootstrap_DynamicResources__Output { + /** + * All :ref:`Listeners ` are provided by a single + * :ref:`LDS ` configuration source. + */ + 'lds_config': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * xdstp:// resource locator for listener collection. + * [#not-implemented-hide:] + */ + 'lds_resources_locator': (string); + /** + * All post-bootstrap :ref:`Cluster ` definitions are + * provided by a single :ref:`CDS ` + * configuration source. + */ + 'cds_config': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * xdstp:// resource locator for cluster collection. + * [#not-implemented-hide:] + */ + 'cds_resources_locator': (string); + /** + * A single :ref:`ADS ` source may be optionally + * specified. This must have :ref:`api_type + * ` :ref:`GRPC + * `. Only + * :ref:`ConfigSources ` that have + * the :ref:`ads ` field set will be + * streamed on the ADS channel. + */ + 'ads_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); +} + +export interface _envoy_config_bootstrap_v3_Bootstrap_StaticResources { + /** + * Static :ref:`Listeners `. These listeners are + * available regardless of LDS configuration. + */ + 'listeners'?: (_envoy_config_listener_v3_Listener)[]; + /** + * If a network based configuration source is specified for :ref:`cds_config + * `, it's necessary + * to have some initial cluster definitions available to allow Envoy to know + * how to speak to the management server. These cluster definitions may not + * use :ref:`EDS ` (i.e. they should be static + * IP or DNS-based). + */ + 'clusters'?: (_envoy_config_cluster_v3_Cluster)[]; + /** + * These static secrets can be used by :ref:`SdsSecretConfig + * ` + */ + 'secrets'?: (_envoy_extensions_transport_sockets_tls_v3_Secret)[]; +} + +export interface _envoy_config_bootstrap_v3_Bootstrap_StaticResources__Output { + /** + * Static :ref:`Listeners `. These listeners are + * available regardless of LDS configuration. + */ + 'listeners': (_envoy_config_listener_v3_Listener__Output)[]; + /** + * If a network based configuration source is specified for :ref:`cds_config + * `, it's necessary + * to have some initial cluster definitions available to allow Envoy to know + * how to speak to the management server. These cluster definitions may not + * use :ref:`EDS ` (i.e. they should be static + * IP or DNS-based). + */ + 'clusters': (_envoy_config_cluster_v3_Cluster__Output)[]; + /** + * These static secrets can be used by :ref:`SdsSecretConfig + * ` + */ + 'secrets': (_envoy_extensions_transport_sockets_tls_v3_Secret__Output)[]; +} + +/** + * Bootstrap :ref:`configuration overview `. + * [#next-free-field: 33] + */ +export interface Bootstrap { + /** + * Node identity to present to the management server and for instance + * identification purposes (e.g. in generated headers). + */ + 'node'?: (_envoy_config_core_v3_Node | null); + /** + * Statically specified resources. + */ + 'static_resources'?: (_envoy_config_bootstrap_v3_Bootstrap_StaticResources | null); + /** + * xDS configuration sources. + */ + 'dynamic_resources'?: (_envoy_config_bootstrap_v3_Bootstrap_DynamicResources | null); + /** + * Configuration for the cluster manager which owns all upstream clusters + * within the server. + */ + 'cluster_manager'?: (_envoy_config_bootstrap_v3_ClusterManager | null); + /** + * Optional file system path to search for startup flag files. + */ + 'flags_path'?: (string); + /** + * Optional set of stats sinks. + */ + 'stats_sinks'?: (_envoy_config_metrics_v3_StatsSink)[]; + /** + * Optional duration between flushes to configured stats sinks. For + * performance reasons Envoy latches counters and only flushes counters and + * gauges at a periodic interval. If not specified the default is 5000ms (5 + * seconds). Only one of `stats_flush_interval` or `stats_flush_on_admin` + * can be set. + * Duration must be at least 1ms and at most 5 min. + */ + 'stats_flush_interval'?: (_google_protobuf_Duration | null); + /** + * Optional watchdog configuration. + * This is for a single watchdog configuration for the entire system. + * Deprecated in favor of *watchdogs* which has finer granularity. + */ + 'watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); + /** + * Configuration for an external tracing provider. + * + * .. attention:: + * This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider + * `. + */ + 'tracing'?: (_envoy_config_trace_v3_Tracing | null); + /** + * Configuration for the local administration HTTP server. + */ + 'admin'?: (_envoy_config_bootstrap_v3_Admin | null); + /** + * Configuration for internal processing of stats. + */ + 'stats_config'?: (_envoy_config_metrics_v3_StatsConfig | null); + /** + * Health discovery service config option. + * (:ref:`core.ApiConfigSource `) + */ + 'hds_config'?: (_envoy_config_core_v3_ApiConfigSource | null); + /** + * Optional overload manager configuration. + */ + 'overload_manager'?: (_envoy_config_overload_v3_OverloadManager | null); + /** + * Enable :ref:`stats for event dispatcher `, defaults to false. + * Note that this records a value for each iteration of the event loop on every thread. This + * should normally be minimal overhead, but when using + * :ref:`statsd `, it will send each observed value + * over the wire individually because the statsd protocol doesn't have any way to represent a + * histogram summary. Be aware that this can be a very large volume of data. + */ + 'enable_dispatcher_stats'?: (boolean); + /** + * Configuration for the runtime configuration provider. If not + * specified, a “null†provider will be used which will result in all defaults + * being used. + */ + 'layered_runtime'?: (_envoy_config_bootstrap_v3_LayeredRuntime | null); + /** + * Optional string which will be used in lieu of x-envoy in prefixing headers. + * + * For example, if this string is present and set to X-Foo, then x-envoy-retry-on will be + * transformed into x-foo-retry-on etc. + * + * Note this applies to the headers Envoy will generate, the headers Envoy will sanitize, and the + * headers Envoy will trust for core code and core extensions only. Be VERY careful making + * changes to this string, especially in multi-layer Envoy deployments or deployments using + * extensions which are not upstream. + */ + 'header_prefix'?: (string); + /** + * Optional proxy version which will be used to set the value of :ref:`server.version statistic + * ` if specified. Envoy will not process this value, it will be sent as is to + * :ref:`stats sinks `. + */ + 'stats_server_version_override'?: (_google_protobuf_UInt64Value | null); + /** + * Always use TCP queries instead of UDP queries for DNS lookups. + * This may be overridden on a per-cluster basis in cds_config, + * when :ref:`dns_resolvers ` and + * :ref:`use_tcp_for_dns_lookups ` are + * specified. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple' API only uses UDP for DNS resolution. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. + */ + 'use_tcp_for_dns_lookups'?: (boolean); + /** + * Specifies optional bootstrap extensions to be instantiated at startup time. + * Each item contains extension specific configuration. + * [#extension-category: envoy.bootstrap] + */ + 'bootstrap_extensions'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Configuration sources that will participate in + * xdstp:// URL authority resolution. The algorithm is as + * follows: + * 1. The authority field is taken from the xdstp:// URL, call + * this *resource_authority*. + * 2. *resource_authority* is compared against the authorities in any peer + * *ConfigSource*. The peer *ConfigSource* is the configuration source + * message which would have been used unconditionally for resolution + * with opaque resource names. If there is a match with an authority, the + * peer *ConfigSource* message is used. + * 3. *resource_authority* is compared sequentially with the authorities in + * each configuration source in *config_sources*. The first *ConfigSource* + * to match wins. + * 4. As a fallback, if no configuration source matches, then + * *default_config_source* is used. + * 5. If *default_config_source* is not specified, resolution fails. + * [#not-implemented-hide:] + */ + 'config_sources'?: (_envoy_config_core_v3_ConfigSource)[]; + /** + * Default configuration source for xdstp:// URLs if all + * other resolution fails. + * [#not-implemented-hide:] + */ + 'default_config_source'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * Optional overriding of default socket interface. The value must be the name of one of the + * socket interface factories initialized through a bootstrap extension + */ + 'default_socket_interface'?: (string); + /** + * Global map of CertificateProvider instances. These instances are referred to by name in the + * :ref:`CommonTlsContext.CertificateProviderInstance.instance_name + * ` + * field. + * [#not-implemented-hide:] + */ + 'certificate_provider_instances'?: ({[key: string]: _envoy_config_core_v3_TypedExtensionConfig}); + /** + * A list of :ref:`Node ` field names + * that will be included in the context parameters of the effective + * xdstp:// URL that is sent in a discovery request when resource + * locators are used for LDS/CDS. Any non-string field will have its JSON + * encoding set as the context parameter value, with the exception of + * metadata, which will be flattened (see example below). The supported field + * names are: + * - "cluster" + * - "id" + * - "locality.region" + * - "locality.sub_zone" + * - "locality.zone" + * - "metadata" + * - "user_agent_build_version.metadata" + * - "user_agent_build_version.version" + * - "user_agent_name" + * - "user_agent_version" + * + * The node context parameters act as a base layer dictionary for the context + * parameters (i.e. more specific resource specific context parameters will + * override). Field names will be prefixed with “udpa.node.†when included in + * context parameters. + * + * For example, if node_context_params is ``["user_agent_name", "metadata"]``, + * the implied context parameters might be:: + * + * node.user_agent_name: "envoy" + * node.metadata.foo: "{\"bar\": \"baz\"}" + * node.metadata.some: "42" + * node.metadata.thing: "\"thing\"" + * + * [#not-implemented-hide:] + */ + 'node_context_params'?: (string)[]; + /** + * Optional watchdogs configuration. + * This is used for specifying different watchdogs for the different subsystems. + * [#extension-category: envoy.guarddog_actions] + */ + 'watchdogs'?: (_envoy_config_bootstrap_v3_Watchdogs | null); + /** + * Specifies optional extensions instantiated at startup time and + * invoked during crash time on the request that caused the crash. + */ + 'fatal_actions'?: (_envoy_config_bootstrap_v3_FatalAction)[]; + /** + * Flush stats to sinks only when queried for on the admin interface. If set, + * a flush timer is not created. Only one of `stats_flush_on_admin` or + * `stats_flush_interval` can be set. + */ + 'stats_flush_on_admin'?: (boolean); + /** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + * This may be overridden on a per-cluster basis in cds_config, when + * :ref:`dns_resolution_config ` + * is specified. + * *dns_resolution_config* will be deprecated once + * :ref:'typed_dns_resolver_config ' + * is fully supported. + */ + 'dns_resolution_config'?: (_envoy_config_core_v3_DnsResolutionConfig | null); + /** + * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, + * or any other DNS resolver types and the related parameters. + * For example, an object of :ref:`DnsResolutionConfig ` + * can be packed into this *typed_dns_resolver_config*. This configuration will replace the + * :ref:'dns_resolution_config ' + * configuration eventually. + * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. + * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, + * this configuration is optional. + * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. + * When *typed_dns_resolver_config* is missing, the default behavior is in place. + * [#not-implemented-hide:] + */ + 'typed_dns_resolver_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Specifies a set of headers that need to be registered as inline header. This configuration + * allows users to customize the inline headers on-demand at Envoy startup without modifying + * Envoy's source code. + * + * Note that the 'set-cookie' header cannot be registered as inline header. + */ + 'inline_headers'?: (_envoy_config_bootstrap_v3_CustomInlineHeader)[]; + 'stats_flush'?: "stats_flush_on_admin"; +} + +/** + * Bootstrap :ref:`configuration overview `. + * [#next-free-field: 33] + */ +export interface Bootstrap__Output { + /** + * Node identity to present to the management server and for instance + * identification purposes (e.g. in generated headers). + */ + 'node': (_envoy_config_core_v3_Node__Output | null); + /** + * Statically specified resources. + */ + 'static_resources': (_envoy_config_bootstrap_v3_Bootstrap_StaticResources__Output | null); + /** + * xDS configuration sources. + */ + 'dynamic_resources': (_envoy_config_bootstrap_v3_Bootstrap_DynamicResources__Output | null); + /** + * Configuration for the cluster manager which owns all upstream clusters + * within the server. + */ + 'cluster_manager': (_envoy_config_bootstrap_v3_ClusterManager__Output | null); + /** + * Optional file system path to search for startup flag files. + */ + 'flags_path': (string); + /** + * Optional set of stats sinks. + */ + 'stats_sinks': (_envoy_config_metrics_v3_StatsSink__Output)[]; + /** + * Optional duration between flushes to configured stats sinks. For + * performance reasons Envoy latches counters and only flushes counters and + * gauges at a periodic interval. If not specified the default is 5000ms (5 + * seconds). Only one of `stats_flush_interval` or `stats_flush_on_admin` + * can be set. + * Duration must be at least 1ms and at most 5 min. + */ + 'stats_flush_interval': (_google_protobuf_Duration__Output | null); + /** + * Optional watchdog configuration. + * This is for a single watchdog configuration for the entire system. + * Deprecated in favor of *watchdogs* which has finer granularity. + */ + 'watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); + /** + * Configuration for an external tracing provider. + * + * .. attention:: + * This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider + * `. + */ + 'tracing': (_envoy_config_trace_v3_Tracing__Output | null); + /** + * Configuration for the local administration HTTP server. + */ + 'admin': (_envoy_config_bootstrap_v3_Admin__Output | null); + /** + * Configuration for internal processing of stats. + */ + 'stats_config': (_envoy_config_metrics_v3_StatsConfig__Output | null); + /** + * Health discovery service config option. + * (:ref:`core.ApiConfigSource `) + */ + 'hds_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); + /** + * Optional overload manager configuration. + */ + 'overload_manager': (_envoy_config_overload_v3_OverloadManager__Output | null); + /** + * Enable :ref:`stats for event dispatcher `, defaults to false. + * Note that this records a value for each iteration of the event loop on every thread. This + * should normally be minimal overhead, but when using + * :ref:`statsd `, it will send each observed value + * over the wire individually because the statsd protocol doesn't have any way to represent a + * histogram summary. Be aware that this can be a very large volume of data. + */ + 'enable_dispatcher_stats': (boolean); + /** + * Configuration for the runtime configuration provider. If not + * specified, a “null†provider will be used which will result in all defaults + * being used. + */ + 'layered_runtime': (_envoy_config_bootstrap_v3_LayeredRuntime__Output | null); + /** + * Optional string which will be used in lieu of x-envoy in prefixing headers. + * + * For example, if this string is present and set to X-Foo, then x-envoy-retry-on will be + * transformed into x-foo-retry-on etc. + * + * Note this applies to the headers Envoy will generate, the headers Envoy will sanitize, and the + * headers Envoy will trust for core code and core extensions only. Be VERY careful making + * changes to this string, especially in multi-layer Envoy deployments or deployments using + * extensions which are not upstream. + */ + 'header_prefix': (string); + /** + * Optional proxy version which will be used to set the value of :ref:`server.version statistic + * ` if specified. Envoy will not process this value, it will be sent as is to + * :ref:`stats sinks `. + */ + 'stats_server_version_override': (_google_protobuf_UInt64Value__Output | null); + /** + * Always use TCP queries instead of UDP queries for DNS lookups. + * This may be overridden on a per-cluster basis in cds_config, + * when :ref:`dns_resolvers ` and + * :ref:`use_tcp_for_dns_lookups ` are + * specified. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple' API only uses UDP for DNS resolution. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. + */ + 'use_tcp_for_dns_lookups': (boolean); + /** + * Specifies optional bootstrap extensions to be instantiated at startup time. + * Each item contains extension specific configuration. + * [#extension-category: envoy.bootstrap] + */ + 'bootstrap_extensions': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Configuration sources that will participate in + * xdstp:// URL authority resolution. The algorithm is as + * follows: + * 1. The authority field is taken from the xdstp:// URL, call + * this *resource_authority*. + * 2. *resource_authority* is compared against the authorities in any peer + * *ConfigSource*. The peer *ConfigSource* is the configuration source + * message which would have been used unconditionally for resolution + * with opaque resource names. If there is a match with an authority, the + * peer *ConfigSource* message is used. + * 3. *resource_authority* is compared sequentially with the authorities in + * each configuration source in *config_sources*. The first *ConfigSource* + * to match wins. + * 4. As a fallback, if no configuration source matches, then + * *default_config_source* is used. + * 5. If *default_config_source* is not specified, resolution fails. + * [#not-implemented-hide:] + */ + 'config_sources': (_envoy_config_core_v3_ConfigSource__Output)[]; + /** + * Default configuration source for xdstp:// URLs if all + * other resolution fails. + * [#not-implemented-hide:] + */ + 'default_config_source': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * Optional overriding of default socket interface. The value must be the name of one of the + * socket interface factories initialized through a bootstrap extension + */ + 'default_socket_interface': (string); + /** + * Global map of CertificateProvider instances. These instances are referred to by name in the + * :ref:`CommonTlsContext.CertificateProviderInstance.instance_name + * ` + * field. + * [#not-implemented-hide:] + */ + 'certificate_provider_instances': ({[key: string]: _envoy_config_core_v3_TypedExtensionConfig__Output}); + /** + * A list of :ref:`Node ` field names + * that will be included in the context parameters of the effective + * xdstp:// URL that is sent in a discovery request when resource + * locators are used for LDS/CDS. Any non-string field will have its JSON + * encoding set as the context parameter value, with the exception of + * metadata, which will be flattened (see example below). The supported field + * names are: + * - "cluster" + * - "id" + * - "locality.region" + * - "locality.sub_zone" + * - "locality.zone" + * - "metadata" + * - "user_agent_build_version.metadata" + * - "user_agent_build_version.version" + * - "user_agent_name" + * - "user_agent_version" + * + * The node context parameters act as a base layer dictionary for the context + * parameters (i.e. more specific resource specific context parameters will + * override). Field names will be prefixed with “udpa.node.†when included in + * context parameters. + * + * For example, if node_context_params is ``["user_agent_name", "metadata"]``, + * the implied context parameters might be:: + * + * node.user_agent_name: "envoy" + * node.metadata.foo: "{\"bar\": \"baz\"}" + * node.metadata.some: "42" + * node.metadata.thing: "\"thing\"" + * + * [#not-implemented-hide:] + */ + 'node_context_params': (string)[]; + /** + * Optional watchdogs configuration. + * This is used for specifying different watchdogs for the different subsystems. + * [#extension-category: envoy.guarddog_actions] + */ + 'watchdogs': (_envoy_config_bootstrap_v3_Watchdogs__Output | null); + /** + * Specifies optional extensions instantiated at startup time and + * invoked during crash time on the request that caused the crash. + */ + 'fatal_actions': (_envoy_config_bootstrap_v3_FatalAction__Output)[]; + /** + * Flush stats to sinks only when queried for on the admin interface. If set, + * a flush timer is not created. Only one of `stats_flush_on_admin` or + * `stats_flush_interval` can be set. + */ + 'stats_flush_on_admin'?: (boolean); + /** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + * This may be overridden on a per-cluster basis in cds_config, when + * :ref:`dns_resolution_config ` + * is specified. + * *dns_resolution_config* will be deprecated once + * :ref:'typed_dns_resolver_config ' + * is fully supported. + */ + 'dns_resolution_config': (_envoy_config_core_v3_DnsResolutionConfig__Output | null); + /** + * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, + * or any other DNS resolver types and the related parameters. + * For example, an object of :ref:`DnsResolutionConfig ` + * can be packed into this *typed_dns_resolver_config*. This configuration will replace the + * :ref:'dns_resolution_config ' + * configuration eventually. + * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. + * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, + * this configuration is optional. + * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. + * When *typed_dns_resolver_config* is missing, the default behavior is in place. + * [#not-implemented-hide:] + */ + 'typed_dns_resolver_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Specifies a set of headers that need to be registered as inline header. This configuration + * allows users to customize the inline headers on-demand at Envoy startup without modifying + * Envoy's source code. + * + * Note that the 'set-cookie' header cannot be registered as inline header. + */ + 'inline_headers': (_envoy_config_bootstrap_v3_CustomInlineHeader__Output)[]; + 'stats_flush': "stats_flush_on_admin"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts new file mode 100644 index 000000000..571b96fb7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts @@ -0,0 +1,99 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { BindConfig as _envoy_config_core_v3_BindConfig, BindConfig__Output as _envoy_config_core_v3_BindConfig__Output } from '../../../../envoy/config/core/v3/BindConfig'; +import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfigSource__Output as _envoy_config_core_v3_ApiConfigSource__Output } from '../../../../envoy/config/core/v3/ApiConfigSource'; +import type { EventServiceConfig as _envoy_config_core_v3_EventServiceConfig, EventServiceConfig__Output as _envoy_config_core_v3_EventServiceConfig__Output } from '../../../../envoy/config/core/v3/EventServiceConfig'; + +export interface _envoy_config_bootstrap_v3_ClusterManager_OutlierDetection { + /** + * Specifies the path to the outlier event log. + */ + 'event_log_path'?: (string); + /** + * [#not-implemented-hide:] + * The gRPC service for the outlier detection event service. + * If empty, outlier detection events won't be sent to a remote endpoint. + */ + 'event_service'?: (_envoy_config_core_v3_EventServiceConfig | null); +} + +export interface _envoy_config_bootstrap_v3_ClusterManager_OutlierDetection__Output { + /** + * Specifies the path to the outlier event log. + */ + 'event_log_path': (string); + /** + * [#not-implemented-hide:] + * The gRPC service for the outlier detection event service. + * If empty, outlier detection events won't be sent to a remote endpoint. + */ + 'event_service': (_envoy_config_core_v3_EventServiceConfig__Output | null); +} + +/** + * Cluster manager :ref:`architecture overview `. + */ +export interface ClusterManager { + /** + * Name of the local cluster (i.e., the cluster that owns the Envoy running + * this configuration). In order to enable :ref:`zone aware routing + * ` this option must be set. + * If *local_cluster_name* is defined then :ref:`clusters + * ` must be defined in the :ref:`Bootstrap + * static cluster resources + * `. This is unrelated to + * the :option:`--service-cluster` option which does not `affect zone aware + * routing `_. + */ + 'local_cluster_name'?: (string); + /** + * Optional global configuration for outlier detection. + */ + 'outlier_detection'?: (_envoy_config_bootstrap_v3_ClusterManager_OutlierDetection | null); + /** + * Optional configuration used to bind newly established upstream connections. + * This may be overridden on a per-cluster basis by upstream_bind_config in the cds_config. + */ + 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig | null); + /** + * A management server endpoint to stream load stats to via + * *StreamLoadStats*. This must have :ref:`api_type + * ` :ref:`GRPC + * `. + */ + 'load_stats_config'?: (_envoy_config_core_v3_ApiConfigSource | null); +} + +/** + * Cluster manager :ref:`architecture overview `. + */ +export interface ClusterManager__Output { + /** + * Name of the local cluster (i.e., the cluster that owns the Envoy running + * this configuration). In order to enable :ref:`zone aware routing + * ` this option must be set. + * If *local_cluster_name* is defined then :ref:`clusters + * ` must be defined in the :ref:`Bootstrap + * static cluster resources + * `. This is unrelated to + * the :option:`--service-cluster` option which does not `affect zone aware + * routing `_. + */ + 'local_cluster_name': (string); + /** + * Optional global configuration for outlier detection. + */ + 'outlier_detection': (_envoy_config_bootstrap_v3_ClusterManager_OutlierDetection__Output | null); + /** + * Optional configuration used to bind newly established upstream connections. + * This may be overridden on a per-cluster basis by upstream_bind_config in the cds_config. + */ + 'upstream_bind_config': (_envoy_config_core_v3_BindConfig__Output | null); + /** + * A management server endpoint to stream load stats to via + * *StreamLoadStats*. This must have :ref:`api_type + * ` :ref:`GRPC + * `. + */ + 'load_stats_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts new file mode 100644 index 000000000..f0e2d29aa --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts @@ -0,0 +1,85 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + + +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +export enum _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType { + REQUEST_HEADER = 0, + REQUEST_TRAILER = 1, + RESPONSE_HEADER = 2, + RESPONSE_TRAILER = 3, +} + +/** + * Used to specify the header that needs to be registered as an inline header. + * + * If request or response contain multiple headers with the same name and the header + * name is registered as an inline header. Then multiple headers will be folded + * into one, and multiple header values will be concatenated by a suitable delimiter. + * The delimiter is generally a comma. + * + * For example, if 'foo' is registered as an inline header, and the headers contains + * the following two headers: + * + * .. code-block:: text + * + * foo: bar + * foo: eep + * + * Then they will eventually be folded into: + * + * .. code-block:: text + * + * foo: bar, eep + * + * Inline headers provide O(1) search performance, but each inline header imposes + * an additional memory overhead on all instances of the corresponding type of + * HeaderMap or TrailerMap. + */ +export interface CustomInlineHeader { + /** + * The name of the header that is expected to be set as the inline header. + */ + 'inline_header_name'?: (string); + /** + * The type of the header that is expected to be set as the inline header. + */ + 'inline_header_type'?: (_envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType | keyof typeof _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType); +} + +/** + * Used to specify the header that needs to be registered as an inline header. + * + * If request or response contain multiple headers with the same name and the header + * name is registered as an inline header. Then multiple headers will be folded + * into one, and multiple header values will be concatenated by a suitable delimiter. + * The delimiter is generally a comma. + * + * For example, if 'foo' is registered as an inline header, and the headers contains + * the following two headers: + * + * .. code-block:: text + * + * foo: bar + * foo: eep + * + * Then they will eventually be folded into: + * + * .. code-block:: text + * + * foo: bar, eep + * + * Inline headers provide O(1) search performance, but each inline header imposes + * an additional memory overhead on all instances of the corresponding type of + * HeaderMap or TrailerMap. + */ +export interface CustomInlineHeader__Output { + /** + * The name of the header that is expected to be set as the inline header. + */ + 'inline_header_name': (string); + /** + * The type of the header that is expected to be set as the inline header. + */ + 'inline_header_type': (keyof typeof _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts new file mode 100644 index 000000000..236afded5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts @@ -0,0 +1,39 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Fatal actions to run while crashing. Actions can be safe (meaning they are + * async-signal safe) or unsafe. We run all safe actions before we run unsafe actions. + * If using an unsafe action that could get stuck or deadlock, it important to + * have an out of band system to terminate the process. + * + * The interface for the extension is ``Envoy::Server::Configuration::FatalAction``. + * *FatalAction* extensions live in the ``envoy.extensions.fatal_actions`` API + * namespace. + */ +export interface FatalAction { + /** + * Extension specific configuration for the action. It's expected to conform + * to the ``Envoy::Server::Configuration::FatalAction`` interface. + */ + 'config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +/** + * Fatal actions to run while crashing. Actions can be safe (meaning they are + * async-signal safe) or unsafe. We run all safe actions before we run unsafe actions. + * If using an unsafe action that could get stuck or deadlock, it important to + * have an out of band system to terminate the process. + * + * The interface for the extension is ``Envoy::Server::Configuration::FatalAction``. + * *FatalAction* extensions live in the ``envoy.extensions.fatal_actions`` API + * namespace. + */ +export interface FatalAction__Output { + /** + * Extension specific configuration for the action. It's expected to conform + * to the ``Envoy::Server::Configuration::FatalAction`` interface. + */ + 'config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts new file mode 100644 index 000000000..3514d3140 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { RuntimeLayer as _envoy_config_bootstrap_v3_RuntimeLayer, RuntimeLayer__Output as _envoy_config_bootstrap_v3_RuntimeLayer__Output } from '../../../../envoy/config/bootstrap/v3/RuntimeLayer'; + +/** + * Runtime :ref:`configuration overview `. + */ +export interface LayeredRuntime { + /** + * The :ref:`layers ` of the runtime. This is ordered + * such that later layers in the list overlay earlier entries. + */ + 'layers'?: (_envoy_config_bootstrap_v3_RuntimeLayer)[]; +} + +/** + * Runtime :ref:`configuration overview `. + */ +export interface LayeredRuntime__Output { + /** + * The :ref:`layers ` of the runtime. This is ordered + * such that later layers in the list overlay earlier entries. + */ + 'layers': (_envoy_config_bootstrap_v3_RuntimeLayer__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts new file mode 100644 index 000000000..4f7713bcf --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts @@ -0,0 +1,77 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; + +/** + * Runtime :ref:`configuration overview ` (deprecated). + */ +export interface Runtime { + /** + * The implementation assumes that the file system tree is accessed via a + * symbolic link. An atomic link swap is used when a new tree should be + * switched to. This parameter specifies the path to the symbolic link. Envoy + * will watch the location for changes and reload the file system tree when + * they happen. If this parameter is not set, there will be no disk based + * runtime. + */ + 'symlink_root'?: (string); + /** + * Specifies the subdirectory to load within the root directory. This is + * useful if multiple systems share the same delivery mechanism. Envoy + * configuration elements can be contained in a dedicated subdirectory. + */ + 'subdirectory'?: (string); + /** + * Specifies an optional subdirectory to load within the root directory. If + * specified and the directory exists, configuration values within this + * directory will override those found in the primary subdirectory. This is + * useful when Envoy is deployed across many different types of servers. + * Sometimes it is useful to have a per service cluster directory for runtime + * configuration. See below for exactly how the override directory is used. + */ + 'override_subdirectory'?: (string); + /** + * Static base runtime. This will be :ref:`overridden + * ` by other runtime layers, e.g. + * disk or admin. This follows the :ref:`runtime protobuf JSON representation + * encoding `. + */ + 'base'?: (_google_protobuf_Struct | null); +} + +/** + * Runtime :ref:`configuration overview ` (deprecated). + */ +export interface Runtime__Output { + /** + * The implementation assumes that the file system tree is accessed via a + * symbolic link. An atomic link swap is used when a new tree should be + * switched to. This parameter specifies the path to the symbolic link. Envoy + * will watch the location for changes and reload the file system tree when + * they happen. If this parameter is not set, there will be no disk based + * runtime. + */ + 'symlink_root': (string); + /** + * Specifies the subdirectory to load within the root directory. This is + * useful if multiple systems share the same delivery mechanism. Envoy + * configuration elements can be contained in a dedicated subdirectory. + */ + 'subdirectory': (string); + /** + * Specifies an optional subdirectory to load within the root directory. If + * specified and the directory exists, configuration values within this + * directory will override those found in the primary subdirectory. This is + * useful when Envoy is deployed across many different types of servers. + * Sometimes it is useful to have a per service cluster directory for runtime + * configuration. See below for exactly how the override directory is used. + */ + 'override_subdirectory': (string); + /** + * Static base runtime. This will be :ref:`overridden + * ` by other runtime layers, e.g. + * disk or admin. This follows the :ref:`runtime protobuf JSON representation + * encoding `. + */ + 'base': (_google_protobuf_Struct__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts new file mode 100644 index 000000000..b072bfa7d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts @@ -0,0 +1,142 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; + +/** + * :ref:`Admin console runtime ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer { +} + +/** + * :ref:`Admin console runtime ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer__Output { +} + +/** + * :ref:`Disk runtime ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer { + /** + * The implementation assumes that the file system tree is accessed via a + * symbolic link. An atomic link swap is used when a new tree should be + * switched to. This parameter specifies the path to the symbolic link. + * Envoy will watch the location for changes and reload the file system tree + * when they happen. See documentation on runtime :ref:`atomicity + * ` for further details on how reloads are + * treated. + */ + 'symlink_root'?: (string); + /** + * Specifies the subdirectory to load within the root directory. This is + * useful if multiple systems share the same delivery mechanism. Envoy + * configuration elements can be contained in a dedicated subdirectory. + */ + 'subdirectory'?: (string); + /** + * :ref:`Append ` the + * service cluster to the path under symlink root. + */ + 'append_service_cluster'?: (boolean); +} + +/** + * :ref:`Disk runtime ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer__Output { + /** + * The implementation assumes that the file system tree is accessed via a + * symbolic link. An atomic link swap is used when a new tree should be + * switched to. This parameter specifies the path to the symbolic link. + * Envoy will watch the location for changes and reload the file system tree + * when they happen. See documentation on runtime :ref:`atomicity + * ` for further details on how reloads are + * treated. + */ + 'symlink_root': (string); + /** + * Specifies the subdirectory to load within the root directory. This is + * useful if multiple systems share the same delivery mechanism. Envoy + * configuration elements can be contained in a dedicated subdirectory. + */ + 'subdirectory': (string); + /** + * :ref:`Append ` the + * service cluster to the path under symlink root. + */ + 'append_service_cluster': (boolean); +} + +/** + * :ref:`Runtime Discovery Service (RTDS) ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer { + /** + * Resource to subscribe to at *rtds_config* for the RTDS layer. + */ + 'name'?: (string); + /** + * RTDS configuration source. + */ + 'rtds_config'?: (_envoy_config_core_v3_ConfigSource | null); +} + +/** + * :ref:`Runtime Discovery Service (RTDS) ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer__Output { + /** + * Resource to subscribe to at *rtds_config* for the RTDS layer. + */ + 'name': (string); + /** + * RTDS configuration source. + */ + 'rtds_config': (_envoy_config_core_v3_ConfigSource__Output | null); +} + +/** + * [#next-free-field: 6] + */ +export interface RuntimeLayer { + /** + * Descriptive name for the runtime layer. This is only used for the runtime + * :http:get:`/runtime` output. + */ + 'name'?: (string); + /** + * :ref:`Static runtime ` layer. + * This follows the :ref:`runtime protobuf JSON representation encoding + * `. Unlike static xDS resources, this static + * layer is overridable by later layers in the runtime virtual filesystem. + */ + 'static_layer'?: (_google_protobuf_Struct | null); + 'disk_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer | null); + 'admin_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer | null); + 'rtds_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer | null); + 'layer_specifier'?: "static_layer"|"disk_layer"|"admin_layer"|"rtds_layer"; +} + +/** + * [#next-free-field: 6] + */ +export interface RuntimeLayer__Output { + /** + * Descriptive name for the runtime layer. This is only used for the runtime + * :http:get:`/runtime` output. + */ + 'name': (string); + /** + * :ref:`Static runtime ` layer. + * This follows the :ref:`runtime protobuf JSON representation encoding + * `. Unlike static xDS resources, this static + * layer is overridable by later layers in the runtime virtual filesystem. + */ + 'static_layer'?: (_google_protobuf_Struct__Output | null); + 'disk_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer__Output | null); + 'admin_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer__Output | null); + 'rtds_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer__Output | null); + 'layer_specifier': "static_layer"|"disk_layer"|"admin_layer"|"rtds_layer"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts new file mode 100644 index 000000000..8cd743b56 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts @@ -0,0 +1,141 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +export interface _envoy_config_bootstrap_v3_Watchdog_WatchdogAction { + /** + * Extension specific configuration for the action. + */ + 'config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + 'event'?: (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent | keyof typeof _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent); +} + +export interface _envoy_config_bootstrap_v3_Watchdog_WatchdogAction__Output { + /** + * Extension specific configuration for the action. + */ + 'config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + 'event': (keyof typeof _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent); +} + +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +/** + * The events are fired in this order: KILL, MULTIKILL, MEGAMISS, MISS. + * Within an event type, actions execute in the order they are configured. + * For KILL/MULTIKILL there is a default PANIC that will run after the + * registered actions and kills the process if it wasn't already killed. + * It might be useful to specify several debug actions, and possibly an + * alternate FATAL action. + */ +export enum _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent { + UNKNOWN = 0, + KILL = 1, + MULTIKILL = 2, + MEGAMISS = 3, + MISS = 4, +} + +/** + * Envoy process watchdog configuration. When configured, this monitors for + * nonresponsive threads and kills the process after the configured thresholds. + * See the :ref:`watchdog documentation ` for more information. + * [#next-free-field: 8] + */ +export interface Watchdog { + /** + * The duration after which Envoy counts a nonresponsive thread in the + * *watchdog_miss* statistic. If not specified the default is 200ms. + */ + 'miss_timeout'?: (_google_protobuf_Duration | null); + /** + * The duration after which Envoy counts a nonresponsive thread in the + * *watchdog_mega_miss* statistic. If not specified the default is + * 1000ms. + */ + 'megamiss_timeout'?: (_google_protobuf_Duration | null); + /** + * If a watched thread has been nonresponsive for this duration, assume a + * programming error and kill the entire Envoy process. Set to 0 to disable + * kill behavior. If not specified the default is 0 (disabled). + */ + 'kill_timeout'?: (_google_protobuf_Duration | null); + /** + * If max(2, ceil(registered_threads * Fraction(*multikill_threshold*))) + * threads have been nonresponsive for at least this duration kill the entire + * Envoy process. Set to 0 to disable this behavior. If not specified the + * default is 0 (disabled). + */ + 'multikill_timeout'?: (_google_protobuf_Duration | null); + /** + * Sets the threshold for *multikill_timeout* in terms of the percentage of + * nonresponsive threads required for the *multikill_timeout*. + * If not specified the default is 0. + */ + 'multikill_threshold'?: (_envoy_type_v3_Percent | null); + /** + * Defines the maximum jitter used to adjust the *kill_timeout* if *kill_timeout* is + * enabled. Enabling this feature would help to reduce risk of synchronized + * watchdog kill events across proxies due to external triggers. Set to 0 to + * disable. If not specified the default is 0 (disabled). + */ + 'max_kill_timeout_jitter'?: (_google_protobuf_Duration | null); + /** + * Register actions that will fire on given WatchDog events. + * See *WatchDogAction* for priority of events. + */ + 'actions'?: (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction)[]; +} + +/** + * Envoy process watchdog configuration. When configured, this monitors for + * nonresponsive threads and kills the process after the configured thresholds. + * See the :ref:`watchdog documentation ` for more information. + * [#next-free-field: 8] + */ +export interface Watchdog__Output { + /** + * The duration after which Envoy counts a nonresponsive thread in the + * *watchdog_miss* statistic. If not specified the default is 200ms. + */ + 'miss_timeout': (_google_protobuf_Duration__Output | null); + /** + * The duration after which Envoy counts a nonresponsive thread in the + * *watchdog_mega_miss* statistic. If not specified the default is + * 1000ms. + */ + 'megamiss_timeout': (_google_protobuf_Duration__Output | null); + /** + * If a watched thread has been nonresponsive for this duration, assume a + * programming error and kill the entire Envoy process. Set to 0 to disable + * kill behavior. If not specified the default is 0 (disabled). + */ + 'kill_timeout': (_google_protobuf_Duration__Output | null); + /** + * If max(2, ceil(registered_threads * Fraction(*multikill_threshold*))) + * threads have been nonresponsive for at least this duration kill the entire + * Envoy process. Set to 0 to disable this behavior. If not specified the + * default is 0 (disabled). + */ + 'multikill_timeout': (_google_protobuf_Duration__Output | null); + /** + * Sets the threshold for *multikill_timeout* in terms of the percentage of + * nonresponsive threads required for the *multikill_timeout*. + * If not specified the default is 0. + */ + 'multikill_threshold': (_envoy_type_v3_Percent__Output | null); + /** + * Defines the maximum jitter used to adjust the *kill_timeout* if *kill_timeout* is + * enabled. Enabling this feature would help to reduce risk of synchronized + * watchdog kill events across proxies due to external triggers. Set to 0 to + * disable. If not specified the default is 0 (disabled). + */ + 'max_kill_timeout_jitter': (_google_protobuf_Duration__Output | null); + /** + * Register actions that will fire on given WatchDog events. + * See *WatchDogAction* for priority of events. + */ + 'actions': (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts new file mode 100644 index 000000000..b478615ea --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Watchdog as _envoy_config_bootstrap_v3_Watchdog, Watchdog__Output as _envoy_config_bootstrap_v3_Watchdog__Output } from '../../../../envoy/config/bootstrap/v3/Watchdog'; + +/** + * Allows you to specify different watchdog configs for different subsystems. + * This allows finer tuned policies for the watchdog. If a subsystem is omitted + * the default values for that system will be used. + */ +export interface Watchdogs { + /** + * Watchdog for the main thread. + */ + 'main_thread_watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); + /** + * Watchdog for the worker threads. + */ + 'worker_watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); +} + +/** + * Allows you to specify different watchdog configs for different subsystems. + * This allows finer tuned policies for the watchdog. If a subsystem is omitted + * the default values for that system will be used. + */ +export interface Watchdogs__Output { + /** + * Watchdog for the main thread. + */ + 'main_thread_watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); + /** + * Watchdog for the worker threads. + */ + 'worker_watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts new file mode 100644 index 000000000..12731b056 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts @@ -0,0 +1,195 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/circuit_breaker.proto + +import type { RoutingPriority as _envoy_config_core_v3_RoutingPriority } from '../../../../envoy/config/core/v3/RoutingPriority'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; + +export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget { + /** + * Specifies the limit on concurrent retries as a percentage of the sum of active requests and + * active pending requests. For example, if there are 100 active requests and the + * budget_percent is set to 25, there may be 25 active retries. + * + * This parameter is optional. Defaults to 20%. + */ + 'budget_percent'?: (_envoy_type_v3_Percent | null); + /** + * Specifies the minimum retry concurrency allowed for the retry budget. The limit on the + * number of active retries may never go below this number. + * + * This parameter is optional. Defaults to 3. + */ + 'min_retry_concurrency'?: (_google_protobuf_UInt32Value | null); +} + +export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget__Output { + /** + * Specifies the limit on concurrent retries as a percentage of the sum of active requests and + * active pending requests. For example, if there are 100 active requests and the + * budget_percent is set to 25, there may be 25 active retries. + * + * This parameter is optional. Defaults to 20%. + */ + 'budget_percent': (_envoy_type_v3_Percent__Output | null); + /** + * Specifies the minimum retry concurrency allowed for the retry budget. The limit on the + * number of active retries may never go below this number. + * + * This parameter is optional. Defaults to 3. + */ + 'min_retry_concurrency': (_google_protobuf_UInt32Value__Output | null); +} + +/** + * A Thresholds defines CircuitBreaker settings for a + * :ref:`RoutingPriority`. + * [#next-free-field: 9] + */ +export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { + /** + * The :ref:`RoutingPriority` + * the specified CircuitBreaker settings apply to. + */ + 'priority'?: (_envoy_config_core_v3_RoutingPriority | keyof typeof _envoy_config_core_v3_RoutingPriority); + /** + * The maximum number of connections that Envoy will make to the upstream + * cluster. If not specified, the default is 1024. + */ + 'max_connections'?: (_google_protobuf_UInt32Value | null); + /** + * The maximum number of pending requests that Envoy will allow to the + * upstream cluster. If not specified, the default is 1024. + */ + 'max_pending_requests'?: (_google_protobuf_UInt32Value | null); + /** + * The maximum number of parallel requests that Envoy will make to the + * upstream cluster. If not specified, the default is 1024. + */ + 'max_requests'?: (_google_protobuf_UInt32Value | null); + /** + * The maximum number of parallel retries that Envoy will allow to the + * upstream cluster. If not specified, the default is 3. + */ + 'max_retries'?: (_google_protobuf_UInt32Value | null); + /** + * Specifies a limit on concurrent retries in relation to the number of active requests. This + * parameter is optional. + * + * .. note:: + * + * If this field is set, the retry budget will override any configured retry circuit + * breaker. + */ + 'retry_budget'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget | null); + /** + * If track_remaining is true, then stats will be published that expose + * the number of resources remaining until the circuit breakers open. If + * not specified, the default is false. + * + * .. note:: + * + * If a retry budget is used in lieu of the max_retries circuit breaker, + * the remaining retry resources remaining will not be tracked. + */ + 'track_remaining'?: (boolean); + /** + * The maximum number of connection pools per cluster that Envoy will concurrently support at + * once. If not specified, the default is unlimited. Set this for clusters which create a + * large number of connection pools. See + * :ref:`Circuit Breaking ` for + * more details. + */ + 'max_connection_pools'?: (_google_protobuf_UInt32Value | null); +} + +/** + * A Thresholds defines CircuitBreaker settings for a + * :ref:`RoutingPriority`. + * [#next-free-field: 9] + */ +export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { + /** + * The :ref:`RoutingPriority` + * the specified CircuitBreaker settings apply to. + */ + 'priority': (keyof typeof _envoy_config_core_v3_RoutingPriority); + /** + * The maximum number of connections that Envoy will make to the upstream + * cluster. If not specified, the default is 1024. + */ + 'max_connections': (_google_protobuf_UInt32Value__Output | null); + /** + * The maximum number of pending requests that Envoy will allow to the + * upstream cluster. If not specified, the default is 1024. + */ + 'max_pending_requests': (_google_protobuf_UInt32Value__Output | null); + /** + * The maximum number of parallel requests that Envoy will make to the + * upstream cluster. If not specified, the default is 1024. + */ + 'max_requests': (_google_protobuf_UInt32Value__Output | null); + /** + * The maximum number of parallel retries that Envoy will allow to the + * upstream cluster. If not specified, the default is 3. + */ + 'max_retries': (_google_protobuf_UInt32Value__Output | null); + /** + * Specifies a limit on concurrent retries in relation to the number of active requests. This + * parameter is optional. + * + * .. note:: + * + * If this field is set, the retry budget will override any configured retry circuit + * breaker. + */ + 'retry_budget': (_envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget__Output | null); + /** + * If track_remaining is true, then stats will be published that expose + * the number of resources remaining until the circuit breakers open. If + * not specified, the default is false. + * + * .. note:: + * + * If a retry budget is used in lieu of the max_retries circuit breaker, + * the remaining retry resources remaining will not be tracked. + */ + 'track_remaining': (boolean); + /** + * The maximum number of connection pools per cluster that Envoy will concurrently support at + * once. If not specified, the default is unlimited. Set this for clusters which create a + * large number of connection pools. See + * :ref:`Circuit Breaking ` for + * more details. + */ + 'max_connection_pools': (_google_protobuf_UInt32Value__Output | null); +} + +/** + * :ref:`Circuit breaking` settings can be + * specified individually for each defined priority. + */ +export interface CircuitBreakers { + /** + * If multiple :ref:`Thresholds` + * are defined with the same :ref:`RoutingPriority`, + * the first one in the list is used. If no Thresholds is defined for a given + * :ref:`RoutingPriority`, the default values + * are used. + */ + 'thresholds'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds)[]; +} + +/** + * :ref:`Circuit breaking` settings can be + * specified individually for each defined priority. + */ +export interface CircuitBreakers__Output { + /** + * If multiple :ref:`Thresholds` + * are defined with the same :ref:`RoutingPriority`, + * the first one in the list is used. If no Thresholds is defined for a given + * :ref:`RoutingPriority`, the default values + * are used. + */ + 'thresholds': (_envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts new file mode 100644 index 000000000..12ec7b633 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts @@ -0,0 +1,2224 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { HealthCheck as _envoy_config_core_v3_HealthCheck, HealthCheck__Output as _envoy_config_core_v3_HealthCheck__Output } from '../../../../envoy/config/core/v3/HealthCheck'; +import type { CircuitBreakers as _envoy_config_cluster_v3_CircuitBreakers, CircuitBreakers__Output as _envoy_config_cluster_v3_CircuitBreakers__Output } from '../../../../envoy/config/cluster/v3/CircuitBreakers'; +import type { Http1ProtocolOptions as _envoy_config_core_v3_Http1ProtocolOptions, Http1ProtocolOptions__Output as _envoy_config_core_v3_Http1ProtocolOptions__Output } from '../../../../envoy/config/core/v3/Http1ProtocolOptions'; +import type { Http2ProtocolOptions as _envoy_config_core_v3_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_config_core_v3_Http2ProtocolOptions__Output } from '../../../../envoy/config/core/v3/Http2ProtocolOptions'; +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { OutlierDetection as _envoy_config_cluster_v3_OutlierDetection, OutlierDetection__Output as _envoy_config_cluster_v3_OutlierDetection__Output } from '../../../../envoy/config/cluster/v3/OutlierDetection'; +import type { BindConfig as _envoy_config_core_v3_BindConfig, BindConfig__Output as _envoy_config_core_v3_BindConfig__Output } from '../../../../envoy/config/core/v3/BindConfig'; +import type { TransportSocket as _envoy_config_core_v3_TransportSocket, TransportSocket__Output as _envoy_config_core_v3_TransportSocket__Output } from '../../../../envoy/config/core/v3/TransportSocket'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { HttpProtocolOptions as _envoy_config_core_v3_HttpProtocolOptions, HttpProtocolOptions__Output as _envoy_config_core_v3_HttpProtocolOptions__Output } from '../../../../envoy/config/core/v3/HttpProtocolOptions'; +import type { UpstreamConnectionOptions as _envoy_config_cluster_v3_UpstreamConnectionOptions, UpstreamConnectionOptions__Output as _envoy_config_cluster_v3_UpstreamConnectionOptions__Output } from '../../../../envoy/config/cluster/v3/UpstreamConnectionOptions'; +import type { ClusterLoadAssignment as _envoy_config_endpoint_v3_ClusterLoadAssignment, ClusterLoadAssignment__Output as _envoy_config_endpoint_v3_ClusterLoadAssignment__Output } from '../../../../envoy/config/endpoint/v3/ClusterLoadAssignment'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Filter as _envoy_config_cluster_v3_Filter, Filter__Output as _envoy_config_cluster_v3_Filter__Output } from '../../../../envoy/config/cluster/v3/Filter'; +import type { LoadBalancingPolicy as _envoy_config_cluster_v3_LoadBalancingPolicy, LoadBalancingPolicy__Output as _envoy_config_cluster_v3_LoadBalancingPolicy__Output } from '../../../../envoy/config/cluster/v3/LoadBalancingPolicy'; +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; +import type { UpstreamHttpProtocolOptions as _envoy_config_core_v3_UpstreamHttpProtocolOptions, UpstreamHttpProtocolOptions__Output as _envoy_config_core_v3_UpstreamHttpProtocolOptions__Output } from '../../../../envoy/config/core/v3/UpstreamHttpProtocolOptions'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { TrackClusterStats as _envoy_config_cluster_v3_TrackClusterStats, TrackClusterStats__Output as _envoy_config_cluster_v3_TrackClusterStats__Output } from '../../../../envoy/config/cluster/v3/TrackClusterStats'; +import type { DnsResolutionConfig as _envoy_config_core_v3_DnsResolutionConfig, DnsResolutionConfig__Output as _envoy_config_core_v3_DnsResolutionConfig__Output } from '../../../../envoy/config/core/v3/DnsResolutionConfig'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { RuntimeDouble as _envoy_config_core_v3_RuntimeDouble, RuntimeDouble__Output as _envoy_config_core_v3_RuntimeDouble__Output } from '../../../../envoy/config/core/v3/RuntimeDouble'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; +import type { DoubleValue as _google_protobuf_DoubleValue, DoubleValue__Output as _google_protobuf_DoubleValue__Output } from '../../../../google/protobuf/DoubleValue'; +import type { Long } from '@grpc/proto-loader'; + +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +export enum _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection { + /** + * Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). + * If :ref:`http2_protocol_options ` are + * present, HTTP2 will be used, otherwise HTTP1.1 will be used. + */ + USE_CONFIGURED_PROTOCOL = 0, + /** + * Use HTTP1.1 or HTTP2, depending on which one is used on the downstream connection. + */ + USE_DOWNSTREAM_PROTOCOL = 1, +} + +/** + * Common configuration for all load balancer implementations. + * [#next-free-field: 8] + */ +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig { + /** + * Configures the :ref:`healthy panic threshold `. + * If not specified, the default is 50%. + * To disable panic mode, set to 0%. + * + * .. note:: + * The specified percent will be truncated to the nearest 1%. + */ + 'healthy_panic_threshold'?: (_envoy_type_v3_Percent | null); + 'zone_aware_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig | null); + 'locality_weighted_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig | null); + /** + * If set, all health check/weight/metadata updates that happen within this duration will be + * merged and delivered in one shot when the duration expires. The start of the duration is when + * the first update happens. This is useful for big clusters, with potentially noisy deploys + * that might trigger excessive CPU usage due to a constant stream of healthcheck state changes + * or metadata updates. The first set of updates to be seen apply immediately (e.g.: a new + * cluster). Please always keep in mind that the use of sandbox technologies may change this + * behavior. + * + * If this is not set, we default to a merge window of 1000ms. To disable it, set the merge + * window to 0. + * + * Note: merging does not apply to cluster membership changes (e.g.: adds/removes); this is + * because merging those updates isn't currently safe. See + * https://github.com/envoyproxy/envoy/pull/3941. + */ + 'update_merge_window'?: (_google_protobuf_Duration | null); + /** + * If set to true, Envoy will :ref:`exclude ` new hosts + * when computing load balancing weights until they have been health checked for the first time. + * This will have no effect unless active health checking is also configured. + */ + 'ignore_new_hosts_until_first_hc'?: (boolean); + /** + * If set to `true`, the cluster manager will drain all existing + * connections to upstream hosts whenever hosts are added or removed from the cluster. + */ + 'close_connections_on_host_set_change'?: (boolean); + /** + * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) + */ + 'consistent_hashing_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig | null); + 'locality_config_specifier'?: "zone_aware_lb_config"|"locality_weighted_lb_config"; +} + +/** + * Common configuration for all load balancer implementations. + * [#next-free-field: 8] + */ +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { + /** + * Configures the :ref:`healthy panic threshold `. + * If not specified, the default is 50%. + * To disable panic mode, set to 0%. + * + * .. note:: + * The specified percent will be truncated to the nearest 1%. + */ + 'healthy_panic_threshold': (_envoy_type_v3_Percent__Output | null); + 'zone_aware_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output | null); + 'locality_weighted_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig__Output | null); + /** + * If set, all health check/weight/metadata updates that happen within this duration will be + * merged and delivered in one shot when the duration expires. The start of the duration is when + * the first update happens. This is useful for big clusters, with potentially noisy deploys + * that might trigger excessive CPU usage due to a constant stream of healthcheck state changes + * or metadata updates. The first set of updates to be seen apply immediately (e.g.: a new + * cluster). Please always keep in mind that the use of sandbox technologies may change this + * behavior. + * + * If this is not set, we default to a merge window of 1000ms. To disable it, set the merge + * window to 0. + * + * Note: merging does not apply to cluster membership changes (e.g.: adds/removes); this is + * because merging those updates isn't currently safe. See + * https://github.com/envoyproxy/envoy/pull/3941. + */ + 'update_merge_window': (_google_protobuf_Duration__Output | null); + /** + * If set to true, Envoy will :ref:`exclude ` new hosts + * when computing load balancing weights until they have been health checked for the first time. + * This will have no effect unless active health checking is also configured. + */ + 'ignore_new_hosts_until_first_hc': (boolean); + /** + * If set to `true`, the cluster manager will drain all existing + * connections to upstream hosts whenever hosts are added or removed from the cluster. + */ + 'close_connections_on_host_set_change': (boolean); + /** + * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) + */ + 'consistent_hashing_lb_config': (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output | null); + 'locality_config_specifier': "zone_aware_lb_config"|"locality_weighted_lb_config"; +} + +/** + * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) + */ +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig { + /** + * If set to `true`, the cluster will use hostname instead of the resolved + * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. + */ + 'use_hostname_for_hashing'?: (boolean); + /** + * Configures percentage of average cluster load to bound per upstream host. For example, with a value of 150 + * no upstream host will get a load more than 1.5 times the average load of all the hosts in the cluster. + * If not specified, the load is not bounded for any upstream host. Typical value for this parameter is between 120 and 200. + * Minimum is 100. + * + * Applies to both Ring Hash and Maglev load balancers. + * + * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified + * `hash_balance_factor`, requests to any upstream host are capped at `hash_balance_factor/100` times the average number of requests + * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing + * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify + * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the + * cascading overflow effect when choosing the next host in the ring/table). + * + * If weights are specified on the hosts, they are respected. + * + * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts + * being probed, so use a higher value if you require better performance. + */ + 'hash_balance_factor'?: (_google_protobuf_UInt32Value | null); +} + +/** + * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) + */ +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output { + /** + * If set to `true`, the cluster will use hostname instead of the resolved + * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. + */ + 'use_hostname_for_hashing': (boolean); + /** + * Configures percentage of average cluster load to bound per upstream host. For example, with a value of 150 + * no upstream host will get a load more than 1.5 times the average load of all the hosts in the cluster. + * If not specified, the load is not bounded for any upstream host. Typical value for this parameter is between 120 and 200. + * Minimum is 100. + * + * Applies to both Ring Hash and Maglev load balancers. + * + * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified + * `hash_balance_factor`, requests to any upstream host are capped at `hash_balance_factor/100` times the average number of requests + * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing + * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify + * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the + * cascading overflow effect when choosing the next host in the ring/table). + * + * If weights are specified on the hosts, they are respected. + * + * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts + * being probed, so use a higher value if you require better performance. + */ + 'hash_balance_factor': (_google_protobuf_UInt32Value__Output | null); +} + +/** + * Extended cluster type. + */ +export interface _envoy_config_cluster_v3_Cluster_CustomClusterType { + /** + * The type of the cluster to instantiate. The name must match a supported cluster type. + */ + 'name'?: (string); + /** + * Cluster specific configuration which depends on the cluster being instantiated. + * See the supported cluster for further documentation. + * [#extension-category: envoy.clusters] + */ + 'typed_config'?: (_google_protobuf_Any | null); +} + +/** + * Extended cluster type. + */ +export interface _envoy_config_cluster_v3_Cluster_CustomClusterType__Output { + /** + * The type of the cluster to instantiate. The name must match a supported cluster type. + */ + 'name': (string); + /** + * Cluster specific configuration which depends on the cluster being instantiated. + * See the supported cluster for further documentation. + * [#extension-category: envoy.clusters] + */ + 'typed_config': (_google_protobuf_Any__Output | null); +} + +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +/** + * Refer to :ref:`service discovery type ` + * for an explanation on each type. + */ +export enum _envoy_config_cluster_v3_Cluster_DiscoveryType { + /** + * Refer to the :ref:`static discovery type` + * for an explanation. + */ + STATIC = 0, + /** + * Refer to the :ref:`strict DNS discovery + * type` + * for an explanation. + */ + STRICT_DNS = 1, + /** + * Refer to the :ref:`logical DNS discovery + * type` + * for an explanation. + */ + LOGICAL_DNS = 2, + /** + * Refer to the :ref:`service discovery type` + * for an explanation. + */ + EDS = 3, + /** + * Refer to the :ref:`original destination discovery + * type` + * for an explanation. + */ + ORIGINAL_DST = 4, +} + +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +/** + * When V4_ONLY is selected, the DNS resolver will only perform a lookup for + * addresses in the IPv4 family. If V6_ONLY is selected, the DNS resolver will + * only perform a lookup for addresses in the IPv6 family. If AUTO is + * specified, the DNS resolver will first perform a lookup for addresses in + * the IPv6 family and fallback to a lookup for addresses in the IPv4 family. + * This is semantically equivalent to a non-existent V6_PREFERRED option. + * AUTO is a legacy name that is more opaque than + * necessary and will be deprecated in favor of V6_PREFERRED in a future major version of the API. + * If V4_PREFERRED is specified, the DNS resolver will first perform a lookup for addresses in the + * IPv4 family and fallback to a lookup for addresses in the IPv6 family. i.e., the callback + * target will only get v6 addresses if there were NO v4 addresses to return. + * For cluster types other than + * :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS`, + * this setting is + * ignored. + * [#next-major-version: deprecate AUTO in favor of a V6_PREFERRED option.] + */ +export enum _envoy_config_cluster_v3_Cluster_DnsLookupFamily { + AUTO = 0, + V4_ONLY = 1, + V6_ONLY = 2, + V4_PREFERRED = 3, +} + +/** + * Only valid when discovery type is EDS. + */ +export interface _envoy_config_cluster_v3_Cluster_EdsClusterConfig { + /** + * Configuration for the source of EDS updates for this Cluster. + */ + 'eds_config'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * Optional alternative to cluster name to present to EDS. This does not + * have the same restrictions as cluster name, i.e. it may be arbitrary + * length. This may be a xdstp:// URL. + */ + 'service_name'?: (string); +} + +/** + * Only valid when discovery type is EDS. + */ +export interface _envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output { + /** + * Configuration for the source of EDS updates for this Cluster. + */ + 'eds_config': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * Optional alternative to cluster name to present to EDS. This does not + * have the same restrictions as cluster name, i.e. it may be arbitrary + * length. This may be a xdstp:// URL. + */ + 'service_name': (string); +} + +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +/** + * The hash function used to hash hosts onto the ketama ring. + */ +export enum _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction { + /** + * Use `xxHash `_, this is the default hash function. + */ + XX_HASH = 0, + /** + * Use `MurmurHash2 `_, this is compatible with + * std:hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled + * on Linux and not macOS. + */ + MURMUR_HASH_2 = 1, +} + +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +/** + * Refer to :ref:`load balancer type ` architecture + * overview section for information on each type. + */ +export enum _envoy_config_cluster_v3_Cluster_LbPolicy { + /** + * Refer to the :ref:`round robin load balancing + * policy` + * for an explanation. + */ + ROUND_ROBIN = 0, + /** + * Refer to the :ref:`least request load balancing + * policy` + * for an explanation. + */ + LEAST_REQUEST = 1, + /** + * Refer to the :ref:`ring hash load balancing + * policy` + * for an explanation. + */ + RING_HASH = 2, + /** + * Refer to the :ref:`random load balancing + * policy` + * for an explanation. + */ + RANDOM = 3, + /** + * Refer to the :ref:`Maglev load balancing policy` + * for an explanation. + */ + MAGLEV = 5, + /** + * This load balancer type must be specified if the configured cluster provides a cluster + * specific load balancer. Consult the configured cluster's documentation for whether to set + * this option or not. + */ + CLUSTER_PROVIDED = 6, + /** + * Use the new :ref:`load_balancing_policy + * ` field to determine the LB policy. + * [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field + * and instead using the new load_balancing_policy field as the one and only mechanism for + * configuring this.] + */ + LOAD_BALANCING_POLICY_CONFIG = 7, +} + +/** + * Optionally divide the endpoints in this cluster into subsets defined by + * endpoint metadata and selected by route and weighted cluster metadata. + * [#next-free-field: 8] + */ +export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { + /** + * The behavior used when no endpoint subset matches the selected route's + * metadata. The value defaults to + * :ref:`NO_FALLBACK`. + */ + 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); + /** + * Specifies the default subset of endpoints used during fallback if + * fallback_policy is + * :ref:`DEFAULT_SUBSET`. + * Each field in default_subset is + * compared to the matching LbEndpoint.Metadata under the *envoy.lb* + * namespace. It is valid for no hosts to match, in which case the behavior + * is the same as a fallback_policy of + * :ref:`NO_FALLBACK`. + */ + 'default_subset'?: (_google_protobuf_Struct | null); + /** + * For each entry, LbEndpoint.Metadata's + * *envoy.lb* namespace is traversed and a subset is created for each unique + * combination of key and value. For example: + * + * .. code-block:: json + * + * { "subset_selectors": [ + * { "keys": [ "version" ] }, + * { "keys": [ "stage", "hardware_type" ] } + * ]} + * + * A subset is matched when the metadata from the selected route and + * weighted cluster contains the same keys and values as the subset's + * metadata. The same host may appear in multiple subsets. + */ + 'subset_selectors'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector)[]; + /** + * If true, routing to subsets will take into account the localities and locality weights of the + * endpoints when making the routing decision. + * + * There are some potential pitfalls associated with enabling this feature, as the resulting + * traffic split after applying both a subset match and locality weights might be undesirable. + * + * Consider for example a situation in which you have 50/50 split across two localities X/Y + * which have 100 hosts each without subsetting. If the subset LB results in X having only 1 + * host selected but Y having 100, then a lot more load is being dumped on the single host in X + * than originally anticipated in the load balancing assignment delivered via EDS. + */ + 'locality_weight_aware'?: (boolean); + /** + * When used with locality_weight_aware, scales the weight of each locality by the ratio + * of hosts in the subset vs hosts in the original subset. This aims to even out the load + * going to an individual locality if said locality is disproportionately affected by the + * subset predicate. + */ + 'scale_locality_weight'?: (boolean); + /** + * If true, when a fallback policy is configured and its corresponding subset fails to find + * a host this will cause any host to be selected instead. + * + * This is useful when using the default subset as the fallback policy, given the default + * subset might become empty. With this option enabled, if that happens the LB will attempt + * to select a host from the entire cluster. + */ + 'panic_mode_any'?: (boolean); + /** + * If true, metadata specified for a metadata key will be matched against the corresponding + * endpoint metadata if the endpoint metadata matches the value exactly OR it is a list value + * and any of the elements in the list matches the criteria. + */ + 'list_as_any'?: (boolean); +} + +/** + * Optionally divide the endpoints in this cluster into subsets defined by + * endpoint metadata and selected by route and weighted cluster metadata. + * [#next-free-field: 8] + */ +export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { + /** + * The behavior used when no endpoint subset matches the selected route's + * metadata. The value defaults to + * :ref:`NO_FALLBACK`. + */ + 'fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); + /** + * Specifies the default subset of endpoints used during fallback if + * fallback_policy is + * :ref:`DEFAULT_SUBSET`. + * Each field in default_subset is + * compared to the matching LbEndpoint.Metadata under the *envoy.lb* + * namespace. It is valid for no hosts to match, in which case the behavior + * is the same as a fallback_policy of + * :ref:`NO_FALLBACK`. + */ + 'default_subset': (_google_protobuf_Struct__Output | null); + /** + * For each entry, LbEndpoint.Metadata's + * *envoy.lb* namespace is traversed and a subset is created for each unique + * combination of key and value. For example: + * + * .. code-block:: json + * + * { "subset_selectors": [ + * { "keys": [ "version" ] }, + * { "keys": [ "stage", "hardware_type" ] } + * ]} + * + * A subset is matched when the metadata from the selected route and + * weighted cluster contains the same keys and values as the subset's + * metadata. The same host may appear in multiple subsets. + */ + 'subset_selectors': (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector__Output)[]; + /** + * If true, routing to subsets will take into account the localities and locality weights of the + * endpoints when making the routing decision. + * + * There are some potential pitfalls associated with enabling this feature, as the resulting + * traffic split after applying both a subset match and locality weights might be undesirable. + * + * Consider for example a situation in which you have 50/50 split across two localities X/Y + * which have 100 hosts each without subsetting. If the subset LB results in X having only 1 + * host selected but Y having 100, then a lot more load is being dumped on the single host in X + * than originally anticipated in the load balancing assignment delivered via EDS. + */ + 'locality_weight_aware': (boolean); + /** + * When used with locality_weight_aware, scales the weight of each locality by the ratio + * of hosts in the subset vs hosts in the original subset. This aims to even out the load + * going to an individual locality if said locality is disproportionately affected by the + * subset predicate. + */ + 'scale_locality_weight': (boolean); + /** + * If true, when a fallback policy is configured and its corresponding subset fails to find + * a host this will cause any host to be selected instead. + * + * This is useful when using the default subset as the fallback policy, given the default + * subset might become empty. With this option enabled, if that happens the LB will attempt + * to select a host from the entire cluster. + */ + 'panic_mode_any': (boolean); + /** + * If true, metadata specified for a metadata key will be matched against the corresponding + * endpoint metadata if the endpoint metadata matches the value exactly OR it is a list value + * and any of the elements in the list matches the criteria. + */ + 'list_as_any': (boolean); +} + +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +/** + * If NO_FALLBACK is selected, a result + * equivalent to no healthy hosts is reported. If ANY_ENDPOINT is selected, + * any cluster endpoint may be returned (subject to policy, health checks, + * etc). If DEFAULT_SUBSET is selected, load balancing is performed over the + * endpoints matching the values from the default_subset field. + */ +export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy { + NO_FALLBACK = 0, + ANY_ENDPOINT = 1, + DEFAULT_SUBSET = 2, +} + +/** + * Specifications for subsets. + */ +export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector { + /** + * List of keys to match with the weighted cluster metadata. + */ + 'keys'?: (string)[]; + /** + * Selects a mode of operation in which each subset has only one host. This mode uses the same rules for + * choosing a host, but updating hosts is faster, especially for large numbers of hosts. + * + * If a match is found to a host, that host will be used regardless of priority levels, unless the host is unhealthy. + * + * Currently, this mode is only supported if `subset_selectors` has only one entry, and `keys` contains + * only one entry. + * + * When this mode is enabled, configurations that contain more than one host with the same metadata value for the single key in `keys` + * will use only one of the hosts with the given key; no requests will be routed to the others. The cluster gauge + * :ref:`lb_subsets_single_host_per_subset_duplicate` indicates how many duplicates are + * present in the current configuration. + */ + 'single_host_per_subset'?: (boolean); + /** + * The behavior used when no endpoint subset matches the selected route's + * metadata. + */ + 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); + /** + * Subset of + * :ref:`keys` used by + * :ref:`KEYS_SUBSET` + * fallback policy. + * It has to be a non empty list if KEYS_SUBSET fallback policy is selected. + * For any other fallback policy the parameter is not used and should not be set. + * Only values also present in + * :ref:`keys` are allowed, but + * `fallback_keys_subset` cannot be equal to `keys`. + */ + 'fallback_keys_subset'?: (string)[]; +} + +/** + * Specifications for subsets. + */ +export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector__Output { + /** + * List of keys to match with the weighted cluster metadata. + */ + 'keys': (string)[]; + /** + * Selects a mode of operation in which each subset has only one host. This mode uses the same rules for + * choosing a host, but updating hosts is faster, especially for large numbers of hosts. + * + * If a match is found to a host, that host will be used regardless of priority levels, unless the host is unhealthy. + * + * Currently, this mode is only supported if `subset_selectors` has only one entry, and `keys` contains + * only one entry. + * + * When this mode is enabled, configurations that contain more than one host with the same metadata value for the single key in `keys` + * will use only one of the hosts with the given key; no requests will be routed to the others. The cluster gauge + * :ref:`lb_subsets_single_host_per_subset_duplicate` indicates how many duplicates are + * present in the current configuration. + */ + 'single_host_per_subset': (boolean); + /** + * The behavior used when no endpoint subset matches the selected route's + * metadata. + */ + 'fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); + /** + * Subset of + * :ref:`keys` used by + * :ref:`KEYS_SUBSET` + * fallback policy. + * It has to be a non empty list if KEYS_SUBSET fallback policy is selected. + * For any other fallback policy the parameter is not used and should not be set. + * Only values also present in + * :ref:`keys` are allowed, but + * `fallback_keys_subset` cannot be equal to `keys`. + */ + 'fallback_keys_subset': (string)[]; +} + +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +/** + * Allows to override top level fallback policy per selector. + */ +export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy { + /** + * If NOT_DEFINED top level config fallback policy is used instead. + */ + NOT_DEFINED = 0, + /** + * If NO_FALLBACK is selected, a result equivalent to no healthy hosts is reported. + */ + NO_FALLBACK = 1, + /** + * If ANY_ENDPOINT is selected, any cluster endpoint may be returned + * (subject to policy, health checks, etc). + */ + ANY_ENDPOINT = 2, + /** + * If DEFAULT_SUBSET is selected, load balancing is performed over the + * endpoints matching the values from the default_subset field. + */ + DEFAULT_SUBSET = 3, + /** + * If KEYS_SUBSET is selected, subset selector matching is performed again with metadata + * keys reduced to + * :ref:`fallback_keys_subset`. + * It allows for a fallback to a different, less specific selector if some of the keys of + * the selector are considered optional. + */ + KEYS_SUBSET = 4, +} + +/** + * Specific configuration for the LeastRequest load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig { + /** + * The number of random healthy hosts from which the host with the fewest active requests will + * be chosen. Defaults to 2 so that we perform two-choice selection if the field is not set. + */ + 'choice_count'?: (_google_protobuf_UInt32Value | null); + /** + * The following formula is used to calculate the dynamic weights when hosts have different load + * balancing weights: + * + * `weight = load_balancing_weight / (active_requests + 1)^active_request_bias` + * + * The larger the active request bias is, the more aggressively active requests will lower the + * effective weight when all host weights are not equal. + * + * `active_request_bias` must be greater than or equal to 0.0. + * + * When `active_request_bias == 0.0` the Least Request Load Balancer doesn't consider the number + * of active requests at the time it picks a host and behaves like the Round Robin Load + * Balancer. + * + * When `active_request_bias > 0.0` the Least Request Load Balancer scales the load balancing + * weight by the number of active requests at the time it does a pick. + * + * The value is cached for performance reasons and refreshed whenever one of the Load Balancer's + * host sets changes, e.g., whenever there is a host membership update or a host load balancing + * weight change. + * + * .. note:: + * This setting only takes effect if all host weights are not equal. + */ + 'active_request_bias'?: (_envoy_config_core_v3_RuntimeDouble | null); + /** + * Configuration for slow start mode. + * If this configuration is not set, slow start will not be not enabled. + */ + 'slow_start_config'?: (_envoy_config_cluster_v3_Cluster_SlowStartConfig | null); +} + +/** + * Specific configuration for the LeastRequest load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output { + /** + * The number of random healthy hosts from which the host with the fewest active requests will + * be chosen. Defaults to 2 so that we perform two-choice selection if the field is not set. + */ + 'choice_count': (_google_protobuf_UInt32Value__Output | null); + /** + * The following formula is used to calculate the dynamic weights when hosts have different load + * balancing weights: + * + * `weight = load_balancing_weight / (active_requests + 1)^active_request_bias` + * + * The larger the active request bias is, the more aggressively active requests will lower the + * effective weight when all host weights are not equal. + * + * `active_request_bias` must be greater than or equal to 0.0. + * + * When `active_request_bias == 0.0` the Least Request Load Balancer doesn't consider the number + * of active requests at the time it picks a host and behaves like the Round Robin Load + * Balancer. + * + * When `active_request_bias > 0.0` the Least Request Load Balancer scales the load balancing + * weight by the number of active requests at the time it does a pick. + * + * The value is cached for performance reasons and refreshed whenever one of the Load Balancer's + * host sets changes, e.g., whenever there is a host membership update or a host load balancing + * weight change. + * + * .. note:: + * This setting only takes effect if all host weights are not equal. + */ + 'active_request_bias': (_envoy_config_core_v3_RuntimeDouble__Output | null); + /** + * Configuration for slow start mode. + * If this configuration is not set, slow start will not be not enabled. + */ + 'slow_start_config': (_envoy_config_cluster_v3_Cluster_SlowStartConfig__Output | null); +} + +/** + * Configuration for :ref:`locality weighted load balancing + * ` + */ +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig { +} + +/** + * Configuration for :ref:`locality weighted load balancing + * ` + */ +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig__Output { +} + +/** + * Specific configuration for the :ref:`Maglev` + * load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig { + /** + * The table size for Maglev hashing. The Maglev aims for ‘minimal disruption’ rather than an absolute guarantee. + * Minimal disruption means that when the set of upstreams changes, a connection will likely be sent to the same + * upstream as it was before. Increasing the table size reduces the amount of disruption. + * The table size must be prime number limited to 5000011. If it is not specified, the default is 65537. + */ + 'table_size'?: (_google_protobuf_UInt64Value | null); +} + +/** + * Specific configuration for the :ref:`Maglev` + * load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output { + /** + * The table size for Maglev hashing. The Maglev aims for ‘minimal disruption’ rather than an absolute guarantee. + * Minimal disruption means that when the set of upstreams changes, a connection will likely be sent to the same + * upstream as it was before. Increasing the table size reduces the amount of disruption. + * The table size must be prime number limited to 5000011. If it is not specified, the default is 65537. + */ + 'table_size': (_google_protobuf_UInt64Value__Output | null); +} + +/** + * Specific configuration for the + * :ref:`Original Destination ` + * load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_OriginalDstLbConfig { + /** + * When true, :ref:`x-envoy-original-dst-host + * ` can be used to override destination + * address. + * + * .. attention:: + * + * This header isn't sanitized by default, so enabling this feature allows HTTP clients to + * route traffic to arbitrary hosts and/or ports, which may have serious security + * consequences. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'use_http_header'?: (boolean); +} + +/** + * Specific configuration for the + * :ref:`Original Destination ` + * load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_OriginalDstLbConfig__Output { + /** + * When true, :ref:`x-envoy-original-dst-host + * ` can be used to override destination + * address. + * + * .. attention:: + * + * This header isn't sanitized by default, so enabling this feature allows HTTP clients to + * route traffic to arbitrary hosts and/or ports, which may have serious security + * consequences. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'use_http_header': (boolean); +} + +export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy { + /** + * Indicates how many streams (rounded up) can be anticipated per-upstream for each + * incoming stream. This is useful for high-QPS or latency-sensitive services. Preconnecting + * will only be done if the upstream is healthy and the cluster has traffic. + * + * For example if this is 2, for an incoming HTTP/1.1 stream, 2 connections will be + * established, one for the new incoming stream, and one for a presumed follow-up stream. For + * HTTP/2, only one connection would be established by default as one connection can + * serve both the original and presumed follow-up stream. + * + * In steady state for non-multiplexed connections a value of 1.5 would mean if there were 100 + * active streams, there would be 100 connections in use, and 50 connections preconnected. + * This might be a useful value for something like short lived single-use connections, + * for example proxying HTTP/1.1 if keep-alive were false and each stream resulted in connection + * termination. It would likely be overkill for long lived connections, such as TCP proxying SMTP + * or regular HTTP/1.1 with keep-alive. For long lived traffic, a value of 1.05 would be more + * reasonable, where for every 100 connections, 5 preconnected connections would be in the queue + * in case of unexpected disconnects where the connection could not be reused. + * + * If this value is not set, or set explicitly to one, Envoy will fetch as many connections + * as needed to serve streams in flight. This means in steady state if a connection is torn down, + * a subsequent streams will pay an upstream-rtt latency penalty waiting for a new connection. + * + * This is limited somewhat arbitrarily to 3 because preconnecting too aggressively can + * harm latency more than the preconnecting helps. + */ + 'per_upstream_preconnect_ratio'?: (_google_protobuf_DoubleValue | null); + /** + * Indicates how many many streams (rounded up) can be anticipated across a cluster for each + * stream, useful for low QPS services. This is currently supported for a subset of + * deterministic non-hash-based load-balancing algorithms (weighted round robin, random). + * Unlike *per_upstream_preconnect_ratio* this preconnects across the upstream instances in a + * cluster, doing best effort predictions of what upstream would be picked next and + * pre-establishing a connection. + * + * Preconnecting will be limited to one preconnect per configured upstream in the cluster and will + * only be done if there are healthy upstreams and the cluster has traffic. + * + * For example if preconnecting is set to 2 for a round robin HTTP/2 cluster, on the first + * incoming stream, 2 connections will be preconnected - one to the first upstream for this + * cluster, one to the second on the assumption there will be a follow-up stream. + * + * If this value is not set, or set explicitly to one, Envoy will fetch as many connections + * as needed to serve streams in flight, so during warm up and in steady state if a connection + * is closed (and per_upstream_preconnect_ratio is not set), there will be a latency hit for + * connection establishment. + * + * If both this and preconnect_ratio are set, Envoy will make sure both predicted needs are met, + * basically preconnecting max(predictive-preconnect, per-upstream-preconnect), for each + * upstream. + */ + 'predictive_preconnect_ratio'?: (_google_protobuf_DoubleValue | null); +} + +export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output { + /** + * Indicates how many streams (rounded up) can be anticipated per-upstream for each + * incoming stream. This is useful for high-QPS or latency-sensitive services. Preconnecting + * will only be done if the upstream is healthy and the cluster has traffic. + * + * For example if this is 2, for an incoming HTTP/1.1 stream, 2 connections will be + * established, one for the new incoming stream, and one for a presumed follow-up stream. For + * HTTP/2, only one connection would be established by default as one connection can + * serve both the original and presumed follow-up stream. + * + * In steady state for non-multiplexed connections a value of 1.5 would mean if there were 100 + * active streams, there would be 100 connections in use, and 50 connections preconnected. + * This might be a useful value for something like short lived single-use connections, + * for example proxying HTTP/1.1 if keep-alive were false and each stream resulted in connection + * termination. It would likely be overkill for long lived connections, such as TCP proxying SMTP + * or regular HTTP/1.1 with keep-alive. For long lived traffic, a value of 1.05 would be more + * reasonable, where for every 100 connections, 5 preconnected connections would be in the queue + * in case of unexpected disconnects where the connection could not be reused. + * + * If this value is not set, or set explicitly to one, Envoy will fetch as many connections + * as needed to serve streams in flight. This means in steady state if a connection is torn down, + * a subsequent streams will pay an upstream-rtt latency penalty waiting for a new connection. + * + * This is limited somewhat arbitrarily to 3 because preconnecting too aggressively can + * harm latency more than the preconnecting helps. + */ + 'per_upstream_preconnect_ratio': (_google_protobuf_DoubleValue__Output | null); + /** + * Indicates how many many streams (rounded up) can be anticipated across a cluster for each + * stream, useful for low QPS services. This is currently supported for a subset of + * deterministic non-hash-based load-balancing algorithms (weighted round robin, random). + * Unlike *per_upstream_preconnect_ratio* this preconnects across the upstream instances in a + * cluster, doing best effort predictions of what upstream would be picked next and + * pre-establishing a connection. + * + * Preconnecting will be limited to one preconnect per configured upstream in the cluster and will + * only be done if there are healthy upstreams and the cluster has traffic. + * + * For example if preconnecting is set to 2 for a round robin HTTP/2 cluster, on the first + * incoming stream, 2 connections will be preconnected - one to the first upstream for this + * cluster, one to the second on the assumption there will be a follow-up stream. + * + * If this value is not set, or set explicitly to one, Envoy will fetch as many connections + * as needed to serve streams in flight, so during warm up and in steady state if a connection + * is closed (and per_upstream_preconnect_ratio is not set), there will be a latency hit for + * connection establishment. + * + * If both this and preconnect_ratio are set, Envoy will make sure both predicted needs are met, + * basically preconnecting max(predictive-preconnect, per-upstream-preconnect), for each + * upstream. + */ + 'predictive_preconnect_ratio': (_google_protobuf_DoubleValue__Output | null); +} + +export interface _envoy_config_cluster_v3_Cluster_RefreshRate { + /** + * Specifies the base interval between refreshes. This parameter is required and must be greater + * than zero and less than + * :ref:`max_interval `. + */ + 'base_interval'?: (_google_protobuf_Duration | null); + /** + * Specifies the maximum interval between refreshes. This parameter is optional, but must be + * greater than or equal to the + * :ref:`base_interval ` if set. The default + * is 10 times the :ref:`base_interval `. + */ + 'max_interval'?: (_google_protobuf_Duration | null); +} + +export interface _envoy_config_cluster_v3_Cluster_RefreshRate__Output { + /** + * Specifies the base interval between refreshes. This parameter is required and must be greater + * than zero and less than + * :ref:`max_interval `. + */ + 'base_interval': (_google_protobuf_Duration__Output | null); + /** + * Specifies the maximum interval between refreshes. This parameter is optional, but must be + * greater than or equal to the + * :ref:`base_interval ` if set. The default + * is 10 times the :ref:`base_interval `. + */ + 'max_interval': (_google_protobuf_Duration__Output | null); +} + +/** + * Specific configuration for the :ref:`RingHash` + * load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig { + /** + * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each + * provided host) the better the request distribution will reflect the desired weights. Defaults + * to 1024 entries, and limited to 8M entries. See also + * :ref:`maximum_ring_size`. + */ + 'minimum_ring_size'?: (_google_protobuf_UInt64Value | null); + /** + * The hash function used to hash hosts onto the ketama ring. The value defaults to + * :ref:`XX_HASH`. + */ + 'hash_function'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction | keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); + /** + * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered + * to further constrain resource use. See also + * :ref:`minimum_ring_size`. + */ + 'maximum_ring_size'?: (_google_protobuf_UInt64Value | null); +} + +/** + * Specific configuration for the :ref:`RingHash` + * load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output { + /** + * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each + * provided host) the better the request distribution will reflect the desired weights. Defaults + * to 1024 entries, and limited to 8M entries. See also + * :ref:`maximum_ring_size`. + */ + 'minimum_ring_size': (_google_protobuf_UInt64Value__Output | null); + /** + * The hash function used to hash hosts onto the ketama ring. The value defaults to + * :ref:`XX_HASH`. + */ + 'hash_function': (keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); + /** + * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered + * to further constrain resource use. See also + * :ref:`minimum_ring_size`. + */ + 'maximum_ring_size': (_google_protobuf_UInt64Value__Output | null); +} + +/** + * Specific configuration for the RoundRobin load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_RoundRobinLbConfig { + /** + * Configuration for slow start mode. + * If this configuration is not set, slow start will not be not enabled. + */ + 'slow_start_config'?: (_envoy_config_cluster_v3_Cluster_SlowStartConfig | null); +} + +/** + * Specific configuration for the RoundRobin load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_RoundRobinLbConfig__Output { + /** + * Configuration for slow start mode. + * If this configuration is not set, slow start will not be not enabled. + */ + 'slow_start_config': (_envoy_config_cluster_v3_Cluster_SlowStartConfig__Output | null); +} + +/** + * Configuration for :ref:`slow start mode `. + */ +export interface _envoy_config_cluster_v3_Cluster_SlowStartConfig { + /** + * Represents the size of slow start window. + * If set, the newly created host remains in slow start mode starting from its creation time + * for the duration of slow start window. + */ + 'slow_start_window'?: (_google_protobuf_Duration | null); + /** + * This parameter controls the speed of traffic increase over the slow start window. Defaults to 1.0, + * so that endpoint would get linearly increasing amount of traffic. + * When increasing the value for this parameter, the speed of traffic ramp-up increases non-linearly. + * The value of aggression parameter should be greater than 0.0. + * By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. + * + * During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: + * `new_weight = weight * time_factor ^ (1 / aggression)`, + * where `time_factor=(time_since_start_seconds / slow_start_time_seconds)`. + * + * As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. + * Once host exits slow start, time_factor and aggression no longer affect its weight. + */ + 'aggression'?: (_envoy_config_core_v3_RuntimeDouble | null); +} + +/** + * Configuration for :ref:`slow start mode `. + */ +export interface _envoy_config_cluster_v3_Cluster_SlowStartConfig__Output { + /** + * Represents the size of slow start window. + * If set, the newly created host remains in slow start mode starting from its creation time + * for the duration of slow start window. + */ + 'slow_start_window': (_google_protobuf_Duration__Output | null); + /** + * This parameter controls the speed of traffic increase over the slow start window. Defaults to 1.0, + * so that endpoint would get linearly increasing amount of traffic. + * When increasing the value for this parameter, the speed of traffic ramp-up increases non-linearly. + * The value of aggression parameter should be greater than 0.0. + * By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. + * + * During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: + * `new_weight = weight * time_factor ^ (1 / aggression)`, + * where `time_factor=(time_since_start_seconds / slow_start_time_seconds)`. + * + * As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. + * Once host exits slow start, time_factor and aggression no longer affect its weight. + */ + 'aggression': (_envoy_config_core_v3_RuntimeDouble__Output | null); +} + +/** + * TransportSocketMatch specifies what transport socket config will be used + * when the match conditions are satisfied. + */ +export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch { + /** + * The name of the match, used in stats generation. + */ + 'name'?: (string); + /** + * Optional endpoint metadata match criteria. + * The connection to the endpoint with metadata matching what is set in this field + * will use the transport socket configuration specified here. + * The endpoint's metadata entry in *envoy.transport_socket_match* is used to match + * against the values specified in this field. + */ + 'match'?: (_google_protobuf_Struct | null); + /** + * The configuration of the transport socket. + * [#extension-category: envoy.transport_sockets.upstream] + */ + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); +} + +/** + * TransportSocketMatch specifies what transport socket config will be used + * when the match conditions are satisfied. + */ +export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch__Output { + /** + * The name of the match, used in stats generation. + */ + 'name': (string); + /** + * Optional endpoint metadata match criteria. + * The connection to the endpoint with metadata matching what is set in this field + * will use the transport socket configuration specified here. + * The endpoint's metadata entry in *envoy.transport_socket_match* is used to match + * against the values specified in this field. + */ + 'match': (_google_protobuf_Struct__Output | null); + /** + * The configuration of the transport socket. + * [#extension-category: envoy.transport_sockets.upstream] + */ + 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); +} + +/** + * Configuration for :ref:`zone aware routing + * `. + */ +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig { + /** + * Configures percentage of requests that will be considered for zone aware routing + * if zone aware routing is configured. If not specified, the default is 100%. + * * :ref:`runtime values `. + * * :ref:`Zone aware routing support `. + */ + 'routing_enabled'?: (_envoy_type_v3_Percent | null); + /** + * Configures minimum upstream cluster size required for zone aware routing + * If upstream cluster size is less than specified, zone aware routing is not performed + * even if zone aware routing is configured. If not specified, the default is 6. + * * :ref:`runtime values `. + * * :ref:`Zone aware routing support `. + */ + 'min_cluster_size'?: (_google_protobuf_UInt64Value | null); + /** + * If set to true, Envoy will not consider any hosts when the cluster is in :ref:`panic + * mode`. Instead, the cluster will fail all + * requests as if all hosts are unhealthy. This can help avoid potentially overwhelming a + * failing service. + */ + 'fail_traffic_on_panic'?: (boolean); +} + +/** + * Configuration for :ref:`zone aware routing + * `. + */ +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output { + /** + * Configures percentage of requests that will be considered for zone aware routing + * if zone aware routing is configured. If not specified, the default is 100%. + * * :ref:`runtime values `. + * * :ref:`Zone aware routing support `. + */ + 'routing_enabled': (_envoy_type_v3_Percent__Output | null); + /** + * Configures minimum upstream cluster size required for zone aware routing + * If upstream cluster size is less than specified, zone aware routing is not performed + * even if zone aware routing is configured. If not specified, the default is 6. + * * :ref:`runtime values `. + * * :ref:`Zone aware routing support `. + */ + 'min_cluster_size': (_google_protobuf_UInt64Value__Output | null); + /** + * If set to true, Envoy will not consider any hosts when the cluster is in :ref:`panic + * mode`. Instead, the cluster will fail all + * requests as if all hosts are unhealthy. This can help avoid potentially overwhelming a + * failing service. + */ + 'fail_traffic_on_panic': (boolean); +} + +/** + * Configuration for a single upstream cluster. + * [#next-free-field: 57] + */ +export interface Cluster { + /** + * Supplies the name of the cluster which must be unique across all clusters. + * The cluster name is used when emitting + * :ref:`statistics ` if :ref:`alt_stat_name + * ` is not provided. + * Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. + */ + 'name'?: (string); + /** + * The :ref:`service discovery type ` + * to use for resolving the cluster. + */ + 'type'?: (_envoy_config_cluster_v3_Cluster_DiscoveryType | keyof typeof _envoy_config_cluster_v3_Cluster_DiscoveryType); + /** + * Configuration to use for EDS updates for the Cluster. + */ + 'eds_cluster_config'?: (_envoy_config_cluster_v3_Cluster_EdsClusterConfig | null); + /** + * The timeout for new network connections to hosts in the cluster. + * If not set, a default value of 5s will be used. + */ + 'connect_timeout'?: (_google_protobuf_Duration | null); + /** + * Soft limit on size of the cluster’s connections read and write buffers. If + * unspecified, an implementation defined default is applied (1MiB). + */ + 'per_connection_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); + /** + * The :ref:`load balancer type ` to use + * when picking a host in the cluster. + */ + 'lb_policy'?: (_envoy_config_cluster_v3_Cluster_LbPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy); + /** + * Optional :ref:`active health checking ` + * configuration for the cluster. If no + * configuration is specified no health checking will be done and all cluster + * members will be considered healthy at all times. + */ + 'health_checks'?: (_envoy_config_core_v3_HealthCheck)[]; + /** + * Optional maximum requests for a single upstream connection. This parameter + * is respected by both the HTTP/1.1 and HTTP/2 connection pool + * implementations. If not specified, there is no limit. Setting this + * parameter to 1 will effectively disable keep alive. + * + * .. attention:: + * This field has been deprecated in favor of the :ref:`max_requests_per_connection ` field. + */ + 'max_requests_per_connection'?: (_google_protobuf_UInt32Value | null); + /** + * Optional :ref:`circuit breaking ` for the cluster. + */ + 'circuit_breakers'?: (_envoy_config_cluster_v3_CircuitBreakers | null); + /** + * Additional options when handling HTTP1 requests. + * This has been deprecated in favor of http_protocol_options fields in the + * :ref:`http_protocol_options ` message. + * http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` + * for example usage. + */ + 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions | null); + /** + * Even if default HTTP2 protocol options are desired, this field must be + * set so that Envoy will assume that the upstream supports HTTP/2 when + * making new HTTP connection pool connections. Currently, Envoy only + * supports prior knowledge for upstream connections. Even if TLS is used + * with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 + * connections to happen over plain text. + * This has been deprecated in favor of http2_protocol_options fields in the + * :ref:`http_protocol_options ` + * message. http2_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` + * for example usage. + */ + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); + /** + * If the DNS refresh rate is specified and the cluster type is either + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, + * this value is used as the cluster’s DNS refresh + * rate. The value configured must be at least 1ms. If this setting is not specified, the + * value defaults to 5000ms. For cluster types other than + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` + * this setting is ignored. + */ + 'dns_refresh_rate'?: (_google_protobuf_Duration | null); + /** + * The DNS IP address resolution policy. If this setting is not specified, the + * value defaults to + * :ref:`AUTO`. + */ + 'dns_lookup_family'?: (_envoy_config_cluster_v3_Cluster_DnsLookupFamily | keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily); + /** + * If DNS resolvers are specified and the cluster type is either + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, + * this value is used to specify the cluster’s dns resolvers. + * If this setting is not specified, the value defaults to the default + * resolver, which uses /etc/resolv.conf for configuration. For cluster types + * other than + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` + * this setting is ignored. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only allows overriding DNS resolvers via system settings. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. + */ + 'dns_resolvers'?: (_envoy_config_core_v3_Address)[]; + /** + * If specified, outlier detection will be enabled for this upstream cluster. + * Each of the configuration values can be overridden via + * :ref:`runtime values `. + */ + 'outlier_detection'?: (_envoy_config_cluster_v3_OutlierDetection | null); + /** + * The interval for removing stale hosts from a cluster type + * :ref:`ORIGINAL_DST`. + * Hosts are considered stale if they have not been used + * as upstream destinations during this interval. New hosts are added + * to original destination clusters on demand as new connections are + * redirected to Envoy, causing the number of hosts in the cluster to + * grow over time. Hosts that are not stale (they are actively used as + * destinations) are kept in the cluster, which allows connections to + * them remain open, saving the latency that would otherwise be spent + * on opening new connections. If this setting is not specified, the + * value defaults to 5000ms. For cluster types other than + * :ref:`ORIGINAL_DST` + * this setting is ignored. + */ + 'cleanup_interval'?: (_google_protobuf_Duration | null); + /** + * Optional configuration used to bind newly established upstream connections. + * This overrides any bind_config specified in the bootstrap proto. + * If the address and port are empty, no bind will be performed. + */ + 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig | null); + /** + * Configuration for load balancing subsetting. + */ + 'lb_subset_config'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig | null); + /** + * Optional configuration for the Ring Hash load balancing policy. + */ + 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig | null); + /** + * Optional custom transport socket implementation to use for upstream connections. + * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and + * :ref:`UpstreamTlsContexts ` in the `typed_config`. + * If no transport socket configuration is specified, new connections + * will be set up with plaintext. + */ + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); + /** + * The Metadata field can be used to provide additional information about the + * cluster. It can be used for stats, logging, and varying filter behavior. + * Fields should use reverse DNS notation to denote which entity within Envoy + * will need the information. For instance, if the metadata is intended for + * the Router filter, the filter name should be specified as *envoy.filters.http.router*. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata | null); + /** + * Determines how Envoy selects the protocol used to speak to upstream hosts. + * This has been deprecated in favor of setting explicit protocol selection + * in the :ref:`http_protocol_options + * ` message. + * http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + */ + 'protocol_selection'?: (_envoy_config_cluster_v3_Cluster_ClusterProtocolSelection | keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); + /** + * Common configuration for all load balancer implementations. + */ + 'common_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig | null); + /** + * An optional alternative to the cluster name to be used for observability. This name is used + * emitting stats for the cluster and access logging the cluster name. This will appear as + * additional information in configuration dumps of a cluster's current status as + * :ref:`observability_name ` + * and as an additional tag "upstream_cluster.name" while tracing. Note: access logging using + * this field is presently enabled with runtime feature + * `envoy.reloadable_features.use_observable_cluster_name`. Any ``:`` in the name will be + * converted to ``_`` when emitting statistics. This should not be confused with :ref:`Router + * Filter Header `. + */ + 'alt_stat_name'?: (string); + /** + * Additional options when handling HTTP requests upstream. These options will be applicable to + * both HTTP1 and HTTP2 requests. + * This has been deprecated in favor of + * :ref:`common_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. + * common_http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` + * for example usage. + */ + 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions | null); + /** + * Optional options for upstream connections. + */ + 'upstream_connection_options'?: (_envoy_config_cluster_v3_UpstreamConnectionOptions | null); + /** + * If an upstream host becomes unhealthy (as determined by the configured health checks + * or outlier detection), immediately close all connections to the failed host. + * + * .. note:: + * + * This is currently only supported for connections created by tcp_proxy. + * + * .. note:: + * + * The current implementation of this feature closes all connections immediately when + * the unhealthy status is detected. If there are a large number of connections open + * to an upstream host that becomes unhealthy, Envoy may spend a substantial amount of + * time exclusively closing these connections, and not processing any other traffic. + */ + 'close_connections_on_host_health_failure'?: (boolean); + /** + * If set to true, Envoy will ignore the health value of a host when processing its removal + * from service discovery. This means that if active health checking is used, Envoy will *not* + * wait for the endpoint to go unhealthy before removing it. + */ + 'ignore_health_on_host_removal'?: (boolean); + /** + * Setting this is required for specifying members of + * :ref:`STATIC`, + * :ref:`STRICT_DNS` + * or :ref:`LOGICAL_DNS` clusters. + * This field supersedes the *hosts* field in the v2 API. + * + * .. attention:: + * + * Setting this allows non-EDS cluster types to contain embedded EDS equivalent + * :ref:`endpoint assignments`. + */ + 'load_assignment'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment | null); + /** + * Optional configuration for the Original Destination load balancing policy. + */ + 'original_dst_lb_config'?: (_envoy_config_cluster_v3_Cluster_OriginalDstLbConfig | null); + /** + * The extension_protocol_options field is used to provide extension-specific protocol options + * for upstream connections. The key should match the extension filter name, such as + * "envoy.filters.network.thrift_proxy". See the extension's documentation for details on + * specific options. + * [#next-major-version: make this a list of typed extensions.] + */ + 'typed_extension_protocol_options'?: ({[key: string]: _google_protobuf_Any}); + /** + * Optional configuration for the LeastRequest load balancing policy. + */ + 'least_request_lb_config'?: (_envoy_config_cluster_v3_Cluster_LeastRequestLbConfig | null); + /** + * The custom cluster type. + */ + 'cluster_type'?: (_envoy_config_cluster_v3_Cluster_CustomClusterType | null); + /** + * Optional configuration for setting cluster's DNS refresh rate. If the value is set to true, + * cluster's DNS refresh rate will be set to resource record's TTL which comes from DNS + * resolution. + */ + 'respect_dns_ttl'?: (boolean); + /** + * An (optional) network filter chain, listed in the order the filters should be applied. + * The chain will be applied to all outgoing connections that Envoy makes to the upstream + * servers of this cluster. + */ + 'filters'?: (_envoy_config_cluster_v3_Filter)[]; + /** + * New mechanism for LB policy configuration. Used only if the + * :ref:`lb_policy` field has the value + * :ref:`LOAD_BALANCING_POLICY_CONFIG`. + */ + 'load_balancing_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy | null); + /** + * [#not-implemented-hide:] + * If present, tells the client where to send load reports via LRS. If not present, the + * client will fall back to a client-side default, which may be either (a) don't send any + * load reports or (b) send load reports for all clusters to a single default server + * (which may be configured in the bootstrap file). + * + * Note that if multiple clusters point to the same LRS server, the client may choose to + * create a separate stream for each cluster or it may choose to coalesce the data for + * multiple clusters onto a single stream. Either way, the client must make sure to send + * the data for any given cluster on no more than one stream. + * + * [#next-major-version: In the v3 API, we should consider restructuring this somehow, + * maybe by allowing LRS to go on the ADS stream, or maybe by moving some of the negotiation + * from the LRS stream here.] + */ + 'lrs_server'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * Configuration to use different transport sockets for different endpoints. + * The entry of *envoy.transport_socket_match* in the + * :ref:`LbEndpoint.Metadata ` + * is used to match against the transport sockets as they appear in the list. The first + * :ref:`match ` is used. + * For example, with the following match + * + * .. code-block:: yaml + * + * transport_socket_matches: + * - name: "enableMTLS" + * match: + * acceptMTLS: true + * transport_socket: + * name: envoy.transport_sockets.tls + * config: { ... } # tls socket configuration + * - name: "defaultToPlaintext" + * match: {} + * transport_socket: + * name: envoy.transport_sockets.raw_buffer + * + * Connections to the endpoints whose metadata value under *envoy.transport_socket_match* + * having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. + * + * If a :ref:`socket match ` with empty match + * criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" + * socket match in case above. + * + * If an endpoint metadata's value under *envoy.transport_socket_match* does not match any + * *TransportSocketMatch*, socket configuration fallbacks to use the *tls_context* or + * *transport_socket* specified in this cluster. + * + * This field allows gradual and flexible transport socket configuration changes. + * + * The metadata of endpoints in EDS can indicate transport socket capabilities. For example, + * an endpoint's metadata can have two key value pairs as "acceptMTLS": "true", + * "acceptPlaintext": "true". While some other endpoints, only accepting plaintext traffic + * has "acceptPlaintext": "true" metadata information. + * + * Then the xDS server can configure the CDS to a client, Envoy A, to send mutual TLS + * traffic for endpoints with "acceptMTLS": "true", by adding a corresponding + * *TransportSocketMatch* in this field. Other client Envoys receive CDS without + * *transport_socket_match* set, and still send plain text traffic to the same cluster. + * + * This field can be used to specify custom transport socket configurations for health + * checks by adding matching key/value pairs in a health check's + * :ref:`transport socket match criteria ` field. + * + * [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] + */ + 'transport_socket_matches'?: (_envoy_config_cluster_v3_Cluster_TransportSocketMatch)[]; + /** + * If the DNS failure refresh rate is specified and the cluster type is either + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, + * this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is + * not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types + * other than :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS` this setting is + * ignored. + */ + 'dns_failure_refresh_rate'?: (_envoy_config_cluster_v3_Cluster_RefreshRate | null); + /** + * Always use TCP queries instead of UDP queries for DNS lookups. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple' API only uses UDP for DNS resolution. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. + */ + 'use_tcp_for_dns_lookups'?: (boolean); + /** + * HTTP protocol options that are applied only to upstream HTTP connections. + * These options apply to all HTTP versions. + * This has been deprecated in favor of + * :ref:`upstream_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. + * upstream_http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` + * for example usage. + */ + 'upstream_http_protocol_options'?: (_envoy_config_core_v3_UpstreamHttpProtocolOptions | null); + /** + * If track_timeout_budgets is true, the :ref:`timeout budget histograms + * ` will be published for each + * request. These show what percentage of a request's per try and global timeout was used. A value + * of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value + * of 100 would indicate that the request took the entirety of the timeout given to it. + * + * .. attention:: + * + * This field has been deprecated in favor of `timeout_budgets`, part of + * :ref:`track_cluster_stats `. + */ + 'track_timeout_budgets'?: (boolean); + /** + * Optional customization and configuration of upstream connection pool, and upstream type. + * + * Currently this field only applies for HTTP traffic but is designed for eventual use for custom + * TCP upstreams. + * + * For HTTP traffic, Envoy will generally take downstream HTTP and send it upstream as upstream + * HTTP, using the http connection pool and the codec from `http2_protocol_options` + * + * For routes where CONNECT termination is configured, Envoy will take downstream CONNECT + * requests and forward the CONNECT payload upstream over raw TCP using the tcp connection pool. + * + * The default pool used is the generic connection pool which creates the HTTP upstream for most + * HTTP requests, and the TCP upstream if CONNECT termination is configured. + * + * If users desire custom connection pool or upstream behavior, for example terminating + * CONNECT only if a custom filter indicates it is appropriate, the custom factories + * can be registered and configured here. + * [#extension-category: envoy.upstreams] + */ + 'upstream_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Configuration to track optional cluster stats. + */ + 'track_cluster_stats'?: (_envoy_config_cluster_v3_TrackClusterStats | null); + /** + * Preconnect configuration for this cluster. + */ + 'preconnect_policy'?: (_envoy_config_cluster_v3_Cluster_PreconnectPolicy | null); + /** + * If `connection_pool_per_downstream_connection` is true, the cluster will use a separate + * connection pool for every downstream connection + */ + 'connection_pool_per_downstream_connection'?: (boolean); + /** + * Optional configuration for the Maglev load balancing policy. + */ + 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig | null); + /** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + * *dns_resolution_config* will be deprecated once + * :ref:'typed_dns_resolver_config ' + * is fully supported. + */ + 'dns_resolution_config'?: (_envoy_config_core_v3_DnsResolutionConfig | null); + /** + * Optional configuration for having cluster readiness block on warm-up. Currently, only applicable for + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`. + * If true, cluster readiness blocks on warm-up. If false, the cluster will complete + * initialization whether or not warm-up has completed. Defaults to true. + */ + 'wait_for_warm_on_init'?: (_google_protobuf_BoolValue | null); + /** + * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, + * or any other DNS resolver types and the related parameters. + * For example, an object of :ref:`DnsResolutionConfig ` + * can be packed into this *typed_dns_resolver_config*. This configuration will replace the + * :ref:'dns_resolution_config ' + * configuration eventually. + * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. + * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, + * this configuration is optional. + * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. + * When *typed_dns_resolver_config* is missing, the default behavior is in place. + * [#not-implemented-hide:] + */ + 'typed_dns_resolver_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Optional configuration for the RoundRobin load balancing policy. + */ + 'round_robin_lb_config'?: (_envoy_config_cluster_v3_Cluster_RoundRobinLbConfig | null); + 'cluster_discovery_type'?: "type"|"cluster_type"; + /** + * Optional configuration for the load balancing algorithm selected by + * LbPolicy. Currently only + * :ref:`RING_HASH`, + * :ref:`MAGLEV` and + * :ref:`LEAST_REQUEST` + * has additional configuration options. + * Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding + * LbPolicy will generate an error at runtime. + */ + 'lb_config'?: "ring_hash_lb_config"|"maglev_lb_config"|"original_dst_lb_config"|"least_request_lb_config"|"round_robin_lb_config"; +} + +/** + * Configuration for a single upstream cluster. + * [#next-free-field: 57] + */ +export interface Cluster__Output { + /** + * Supplies the name of the cluster which must be unique across all clusters. + * The cluster name is used when emitting + * :ref:`statistics ` if :ref:`alt_stat_name + * ` is not provided. + * Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. + */ + 'name': (string); + /** + * The :ref:`service discovery type ` + * to use for resolving the cluster. + */ + 'type'?: (keyof typeof _envoy_config_cluster_v3_Cluster_DiscoveryType); + /** + * Configuration to use for EDS updates for the Cluster. + */ + 'eds_cluster_config': (_envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output | null); + /** + * The timeout for new network connections to hosts in the cluster. + * If not set, a default value of 5s will be used. + */ + 'connect_timeout': (_google_protobuf_Duration__Output | null); + /** + * Soft limit on size of the cluster’s connections read and write buffers. If + * unspecified, an implementation defined default is applied (1MiB). + */ + 'per_connection_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); + /** + * The :ref:`load balancer type ` to use + * when picking a host in the cluster. + */ + 'lb_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy); + /** + * Optional :ref:`active health checking ` + * configuration for the cluster. If no + * configuration is specified no health checking will be done and all cluster + * members will be considered healthy at all times. + */ + 'health_checks': (_envoy_config_core_v3_HealthCheck__Output)[]; + /** + * Optional maximum requests for a single upstream connection. This parameter + * is respected by both the HTTP/1.1 and HTTP/2 connection pool + * implementations. If not specified, there is no limit. Setting this + * parameter to 1 will effectively disable keep alive. + * + * .. attention:: + * This field has been deprecated in favor of the :ref:`max_requests_per_connection ` field. + */ + 'max_requests_per_connection': (_google_protobuf_UInt32Value__Output | null); + /** + * Optional :ref:`circuit breaking ` for the cluster. + */ + 'circuit_breakers': (_envoy_config_cluster_v3_CircuitBreakers__Output | null); + /** + * Additional options when handling HTTP1 requests. + * This has been deprecated in favor of http_protocol_options fields in the + * :ref:`http_protocol_options ` message. + * http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` + * for example usage. + */ + 'http_protocol_options': (_envoy_config_core_v3_Http1ProtocolOptions__Output | null); + /** + * Even if default HTTP2 protocol options are desired, this field must be + * set so that Envoy will assume that the upstream supports HTTP/2 when + * making new HTTP connection pool connections. Currently, Envoy only + * supports prior knowledge for upstream connections. Even if TLS is used + * with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 + * connections to happen over plain text. + * This has been deprecated in favor of http2_protocol_options fields in the + * :ref:`http_protocol_options ` + * message. http2_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` + * for example usage. + */ + 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); + /** + * If the DNS refresh rate is specified and the cluster type is either + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, + * this value is used as the cluster’s DNS refresh + * rate. The value configured must be at least 1ms. If this setting is not specified, the + * value defaults to 5000ms. For cluster types other than + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` + * this setting is ignored. + */ + 'dns_refresh_rate': (_google_protobuf_Duration__Output | null); + /** + * The DNS IP address resolution policy. If this setting is not specified, the + * value defaults to + * :ref:`AUTO`. + */ + 'dns_lookup_family': (keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily); + /** + * If DNS resolvers are specified and the cluster type is either + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, + * this value is used to specify the cluster’s dns resolvers. + * If this setting is not specified, the value defaults to the default + * resolver, which uses /etc/resolv.conf for configuration. For cluster types + * other than + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` + * this setting is ignored. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only allows overriding DNS resolvers via system settings. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. + */ + 'dns_resolvers': (_envoy_config_core_v3_Address__Output)[]; + /** + * If specified, outlier detection will be enabled for this upstream cluster. + * Each of the configuration values can be overridden via + * :ref:`runtime values `. + */ + 'outlier_detection': (_envoy_config_cluster_v3_OutlierDetection__Output | null); + /** + * The interval for removing stale hosts from a cluster type + * :ref:`ORIGINAL_DST`. + * Hosts are considered stale if they have not been used + * as upstream destinations during this interval. New hosts are added + * to original destination clusters on demand as new connections are + * redirected to Envoy, causing the number of hosts in the cluster to + * grow over time. Hosts that are not stale (they are actively used as + * destinations) are kept in the cluster, which allows connections to + * them remain open, saving the latency that would otherwise be spent + * on opening new connections. If this setting is not specified, the + * value defaults to 5000ms. For cluster types other than + * :ref:`ORIGINAL_DST` + * this setting is ignored. + */ + 'cleanup_interval': (_google_protobuf_Duration__Output | null); + /** + * Optional configuration used to bind newly established upstream connections. + * This overrides any bind_config specified in the bootstrap proto. + * If the address and port are empty, no bind will be performed. + */ + 'upstream_bind_config': (_envoy_config_core_v3_BindConfig__Output | null); + /** + * Configuration for load balancing subsetting. + */ + 'lb_subset_config': (_envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output | null); + /** + * Optional configuration for the Ring Hash load balancing policy. + */ + 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output | null); + /** + * Optional custom transport socket implementation to use for upstream connections. + * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and + * :ref:`UpstreamTlsContexts ` in the `typed_config`. + * If no transport socket configuration is specified, new connections + * will be set up with plaintext. + */ + 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); + /** + * The Metadata field can be used to provide additional information about the + * cluster. It can be used for stats, logging, and varying filter behavior. + * Fields should use reverse DNS notation to denote which entity within Envoy + * will need the information. For instance, if the metadata is intended for + * the Router filter, the filter name should be specified as *envoy.filters.http.router*. + */ + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); + /** + * Determines how Envoy selects the protocol used to speak to upstream hosts. + * This has been deprecated in favor of setting explicit protocol selection + * in the :ref:`http_protocol_options + * ` message. + * http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + */ + 'protocol_selection': (keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); + /** + * Common configuration for all load balancer implementations. + */ + 'common_lb_config': (_envoy_config_cluster_v3_Cluster_CommonLbConfig__Output | null); + /** + * An optional alternative to the cluster name to be used for observability. This name is used + * emitting stats for the cluster and access logging the cluster name. This will appear as + * additional information in configuration dumps of a cluster's current status as + * :ref:`observability_name ` + * and as an additional tag "upstream_cluster.name" while tracing. Note: access logging using + * this field is presently enabled with runtime feature + * `envoy.reloadable_features.use_observable_cluster_name`. Any ``:`` in the name will be + * converted to ``_`` when emitting statistics. This should not be confused with :ref:`Router + * Filter Header `. + */ + 'alt_stat_name': (string); + /** + * Additional options when handling HTTP requests upstream. These options will be applicable to + * both HTTP1 and HTTP2 requests. + * This has been deprecated in favor of + * :ref:`common_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. + * common_http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` + * for example usage. + */ + 'common_http_protocol_options': (_envoy_config_core_v3_HttpProtocolOptions__Output | null); + /** + * Optional options for upstream connections. + */ + 'upstream_connection_options': (_envoy_config_cluster_v3_UpstreamConnectionOptions__Output | null); + /** + * If an upstream host becomes unhealthy (as determined by the configured health checks + * or outlier detection), immediately close all connections to the failed host. + * + * .. note:: + * + * This is currently only supported for connections created by tcp_proxy. + * + * .. note:: + * + * The current implementation of this feature closes all connections immediately when + * the unhealthy status is detected. If there are a large number of connections open + * to an upstream host that becomes unhealthy, Envoy may spend a substantial amount of + * time exclusively closing these connections, and not processing any other traffic. + */ + 'close_connections_on_host_health_failure': (boolean); + /** + * If set to true, Envoy will ignore the health value of a host when processing its removal + * from service discovery. This means that if active health checking is used, Envoy will *not* + * wait for the endpoint to go unhealthy before removing it. + */ + 'ignore_health_on_host_removal': (boolean); + /** + * Setting this is required for specifying members of + * :ref:`STATIC`, + * :ref:`STRICT_DNS` + * or :ref:`LOGICAL_DNS` clusters. + * This field supersedes the *hosts* field in the v2 API. + * + * .. attention:: + * + * Setting this allows non-EDS cluster types to contain embedded EDS equivalent + * :ref:`endpoint assignments`. + */ + 'load_assignment': (_envoy_config_endpoint_v3_ClusterLoadAssignment__Output | null); + /** + * Optional configuration for the Original Destination load balancing policy. + */ + 'original_dst_lb_config'?: (_envoy_config_cluster_v3_Cluster_OriginalDstLbConfig__Output | null); + /** + * The extension_protocol_options field is used to provide extension-specific protocol options + * for upstream connections. The key should match the extension filter name, such as + * "envoy.filters.network.thrift_proxy". See the extension's documentation for details on + * specific options. + * [#next-major-version: make this a list of typed extensions.] + */ + 'typed_extension_protocol_options': ({[key: string]: _google_protobuf_Any__Output}); + /** + * Optional configuration for the LeastRequest load balancing policy. + */ + 'least_request_lb_config'?: (_envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output | null); + /** + * The custom cluster type. + */ + 'cluster_type'?: (_envoy_config_cluster_v3_Cluster_CustomClusterType__Output | null); + /** + * Optional configuration for setting cluster's DNS refresh rate. If the value is set to true, + * cluster's DNS refresh rate will be set to resource record's TTL which comes from DNS + * resolution. + */ + 'respect_dns_ttl': (boolean); + /** + * An (optional) network filter chain, listed in the order the filters should be applied. + * The chain will be applied to all outgoing connections that Envoy makes to the upstream + * servers of this cluster. + */ + 'filters': (_envoy_config_cluster_v3_Filter__Output)[]; + /** + * New mechanism for LB policy configuration. Used only if the + * :ref:`lb_policy` field has the value + * :ref:`LOAD_BALANCING_POLICY_CONFIG`. + */ + 'load_balancing_policy': (_envoy_config_cluster_v3_LoadBalancingPolicy__Output | null); + /** + * [#not-implemented-hide:] + * If present, tells the client where to send load reports via LRS. If not present, the + * client will fall back to a client-side default, which may be either (a) don't send any + * load reports or (b) send load reports for all clusters to a single default server + * (which may be configured in the bootstrap file). + * + * Note that if multiple clusters point to the same LRS server, the client may choose to + * create a separate stream for each cluster or it may choose to coalesce the data for + * multiple clusters onto a single stream. Either way, the client must make sure to send + * the data for any given cluster on no more than one stream. + * + * [#next-major-version: In the v3 API, we should consider restructuring this somehow, + * maybe by allowing LRS to go on the ADS stream, or maybe by moving some of the negotiation + * from the LRS stream here.] + */ + 'lrs_server': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * Configuration to use different transport sockets for different endpoints. + * The entry of *envoy.transport_socket_match* in the + * :ref:`LbEndpoint.Metadata ` + * is used to match against the transport sockets as they appear in the list. The first + * :ref:`match ` is used. + * For example, with the following match + * + * .. code-block:: yaml + * + * transport_socket_matches: + * - name: "enableMTLS" + * match: + * acceptMTLS: true + * transport_socket: + * name: envoy.transport_sockets.tls + * config: { ... } # tls socket configuration + * - name: "defaultToPlaintext" + * match: {} + * transport_socket: + * name: envoy.transport_sockets.raw_buffer + * + * Connections to the endpoints whose metadata value under *envoy.transport_socket_match* + * having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. + * + * If a :ref:`socket match ` with empty match + * criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" + * socket match in case above. + * + * If an endpoint metadata's value under *envoy.transport_socket_match* does not match any + * *TransportSocketMatch*, socket configuration fallbacks to use the *tls_context* or + * *transport_socket* specified in this cluster. + * + * This field allows gradual and flexible transport socket configuration changes. + * + * The metadata of endpoints in EDS can indicate transport socket capabilities. For example, + * an endpoint's metadata can have two key value pairs as "acceptMTLS": "true", + * "acceptPlaintext": "true". While some other endpoints, only accepting plaintext traffic + * has "acceptPlaintext": "true" metadata information. + * + * Then the xDS server can configure the CDS to a client, Envoy A, to send mutual TLS + * traffic for endpoints with "acceptMTLS": "true", by adding a corresponding + * *TransportSocketMatch* in this field. Other client Envoys receive CDS without + * *transport_socket_match* set, and still send plain text traffic to the same cluster. + * + * This field can be used to specify custom transport socket configurations for health + * checks by adding matching key/value pairs in a health check's + * :ref:`transport socket match criteria ` field. + * + * [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] + */ + 'transport_socket_matches': (_envoy_config_cluster_v3_Cluster_TransportSocketMatch__Output)[]; + /** + * If the DNS failure refresh rate is specified and the cluster type is either + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, + * this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is + * not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types + * other than :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS` this setting is + * ignored. + */ + 'dns_failure_refresh_rate': (_envoy_config_cluster_v3_Cluster_RefreshRate__Output | null); + /** + * Always use TCP queries instead of UDP queries for DNS lookups. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple' API only uses UDP for DNS resolution. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. + */ + 'use_tcp_for_dns_lookups': (boolean); + /** + * HTTP protocol options that are applied only to upstream HTTP connections. + * These options apply to all HTTP versions. + * This has been deprecated in favor of + * :ref:`upstream_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. + * upstream_http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` + * for example usage. + */ + 'upstream_http_protocol_options': (_envoy_config_core_v3_UpstreamHttpProtocolOptions__Output | null); + /** + * If track_timeout_budgets is true, the :ref:`timeout budget histograms + * ` will be published for each + * request. These show what percentage of a request's per try and global timeout was used. A value + * of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value + * of 100 would indicate that the request took the entirety of the timeout given to it. + * + * .. attention:: + * + * This field has been deprecated in favor of `timeout_budgets`, part of + * :ref:`track_cluster_stats `. + */ + 'track_timeout_budgets': (boolean); + /** + * Optional customization and configuration of upstream connection pool, and upstream type. + * + * Currently this field only applies for HTTP traffic but is designed for eventual use for custom + * TCP upstreams. + * + * For HTTP traffic, Envoy will generally take downstream HTTP and send it upstream as upstream + * HTTP, using the http connection pool and the codec from `http2_protocol_options` + * + * For routes where CONNECT termination is configured, Envoy will take downstream CONNECT + * requests and forward the CONNECT payload upstream over raw TCP using the tcp connection pool. + * + * The default pool used is the generic connection pool which creates the HTTP upstream for most + * HTTP requests, and the TCP upstream if CONNECT termination is configured. + * + * If users desire custom connection pool or upstream behavior, for example terminating + * CONNECT only if a custom filter indicates it is appropriate, the custom factories + * can be registered and configured here. + * [#extension-category: envoy.upstreams] + */ + 'upstream_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Configuration to track optional cluster stats. + */ + 'track_cluster_stats': (_envoy_config_cluster_v3_TrackClusterStats__Output | null); + /** + * Preconnect configuration for this cluster. + */ + 'preconnect_policy': (_envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output | null); + /** + * If `connection_pool_per_downstream_connection` is true, the cluster will use a separate + * connection pool for every downstream connection + */ + 'connection_pool_per_downstream_connection': (boolean); + /** + * Optional configuration for the Maglev load balancing policy. + */ + 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output | null); + /** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + * *dns_resolution_config* will be deprecated once + * :ref:'typed_dns_resolver_config ' + * is fully supported. + */ + 'dns_resolution_config': (_envoy_config_core_v3_DnsResolutionConfig__Output | null); + /** + * Optional configuration for having cluster readiness block on warm-up. Currently, only applicable for + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`. + * If true, cluster readiness blocks on warm-up. If false, the cluster will complete + * initialization whether or not warm-up has completed. Defaults to true. + */ + 'wait_for_warm_on_init': (_google_protobuf_BoolValue__Output | null); + /** + * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, + * or any other DNS resolver types and the related parameters. + * For example, an object of :ref:`DnsResolutionConfig ` + * can be packed into this *typed_dns_resolver_config*. This configuration will replace the + * :ref:'dns_resolution_config ' + * configuration eventually. + * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. + * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, + * this configuration is optional. + * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. + * When *typed_dns_resolver_config* is missing, the default behavior is in place. + * [#not-implemented-hide:] + */ + 'typed_dns_resolver_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Optional configuration for the RoundRobin load balancing policy. + */ + 'round_robin_lb_config'?: (_envoy_config_cluster_v3_Cluster_RoundRobinLbConfig__Output | null); + 'cluster_discovery_type': "type"|"cluster_type"; + /** + * Optional configuration for the load balancing algorithm selected by + * LbPolicy. Currently only + * :ref:`RING_HASH`, + * :ref:`MAGLEV` and + * :ref:`LEAST_REQUEST` + * has additional configuration options. + * Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding + * LbPolicy will generate an error at runtime. + */ + 'lb_config': "ring_hash_lb_config"|"maglev_lb_config"|"original_dst_lb_config"|"least_request_lb_config"|"round_robin_lb_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts new file mode 100644 index 000000000..8b1394d4b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts @@ -0,0 +1,19 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +import type { CollectionEntry as _xds_core_v3_CollectionEntry, CollectionEntry__Output as _xds_core_v3_CollectionEntry__Output } from '../../../../xds/core/v3/CollectionEntry'; + +/** + * Cluster list collections. Entries are *Cluster* resources or references. + * [#not-implemented-hide:] + */ +export interface ClusterCollection { + 'entries'?: (_xds_core_v3_CollectionEntry | null); +} + +/** + * Cluster list collections. Entries are *Cluster* resources or references. + * [#not-implemented-hide:] + */ +export interface ClusterCollection__Output { + 'entries': (_xds_core_v3_CollectionEntry__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts new file mode 100644 index 000000000..9d9031b68 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts @@ -0,0 +1,31 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/filter.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +export interface Filter { + /** + * The name of the filter to instantiate. The name must match a + * supported upstream filter. Note that Envoy's :ref:`downstream network + * filters ` are not valid upstream filters. + */ + 'name'?: (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + */ + 'typed_config'?: (_google_protobuf_Any | null); +} + +export interface Filter__Output { + /** + * The name of the filter to instantiate. The name must match a + * supported upstream filter. Note that Envoy's :ref:`downstream network + * filters ` are not valid upstream filters. + */ + 'name': (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + */ + 'typed_config': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts new file mode 100644 index 000000000..78d4a6bfd --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts @@ -0,0 +1,71 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy { + 'typed_extension_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy__Output { + 'typed_extension_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} + +/** + * Extensible load balancing policy configuration. + * + * Every LB policy defined via this mechanism will be identified via a unique name using reverse + * DNS notation. If the policy needs configuration parameters, it must define a message for its + * own configuration, which will be stored in the config field. The name of the policy will tell + * clients which type of message they should expect to see in the config field. + * + * Note that there are cases where it is useful to be able to independently select LB policies + * for choosing a locality and for choosing an endpoint within that locality. For example, a + * given deployment may always use the same policy to choose the locality, but for choosing the + * endpoint within the locality, some clusters may use weighted-round-robin, while others may + * use some sort of session-based balancing. + * + * This can be accomplished via hierarchical LB policies, where the parent LB policy creates a + * child LB policy for each locality. For each request, the parent chooses the locality and then + * delegates to the child policy for that locality to choose the endpoint within the locality. + * + * To facilitate this, the config message for the top-level LB policy may include a field of + * type LoadBalancingPolicy that specifies the child policy. + */ +export interface LoadBalancingPolicy { + /** + * Each client will iterate over the list in order and stop at the first policy that it + * supports. This provides a mechanism for starting to use new LB policies that are not yet + * supported by all clients. + */ + 'policies'?: (_envoy_config_cluster_v3_LoadBalancingPolicy_Policy)[]; +} + +/** + * Extensible load balancing policy configuration. + * + * Every LB policy defined via this mechanism will be identified via a unique name using reverse + * DNS notation. If the policy needs configuration parameters, it must define a message for its + * own configuration, which will be stored in the config field. The name of the policy will tell + * clients which type of message they should expect to see in the config field. + * + * Note that there are cases where it is useful to be able to independently select LB policies + * for choosing a locality and for choosing an endpoint within that locality. For example, a + * given deployment may always use the same policy to choose the locality, but for choosing the + * endpoint within the locality, some clusters may use weighted-round-robin, while others may + * use some sort of session-based balancing. + * + * This can be accomplished via hierarchical LB policies, where the parent LB policy creates a + * child LB policy for each locality. For each request, the parent chooses the locality and then + * delegates to the child policy for that locality to choose the endpoint within the locality. + * + * To facilitate this, the config message for the top-level LB policy may include a field of + * type LoadBalancingPolicy that specifies the child policy. + */ +export interface LoadBalancingPolicy__Output { + /** + * Each client will iterate over the list in order and stop at the first policy that it + * supports. This provides a mechanism for starting to use new LB policies that are not yet + * supported by all clients. + */ + 'policies': (_envoy_config_cluster_v3_LoadBalancingPolicy_Policy__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts new file mode 100644 index 000000000..789004ad4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts @@ -0,0 +1,314 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/outlier_detection.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * See the :ref:`architecture overview ` for + * more information on outlier detection. + * [#next-free-field: 22] + */ +export interface OutlierDetection { + /** + * The number of consecutive 5xx responses or local origin errors that are mapped + * to 5xx error codes before a consecutive 5xx ejection + * occurs. Defaults to 5. + */ + 'consecutive_5xx'?: (_google_protobuf_UInt32Value | null); + /** + * The time interval between ejection analysis sweeps. This can result in + * both new ejections as well as hosts being returned to service. Defaults + * to 10000ms or 10s. + */ + 'interval'?: (_google_protobuf_Duration | null); + /** + * The base time that a host is ejected for. The real time is equal to the + * base time multiplied by the number of times the host has been ejected and is + * capped by :ref:`max_ejection_time`. + * Defaults to 30000ms or 30s. + */ + 'base_ejection_time'?: (_google_protobuf_Duration | null); + /** + * The maximum % of an upstream cluster that can be ejected due to outlier + * detection. Defaults to 10% but will eject at least one host regardless of the value. + */ + 'max_ejection_percent'?: (_google_protobuf_UInt32Value | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through consecutive 5xx. This setting can be used to disable + * ejection or to ramp it up slowly. Defaults to 100. + */ + 'enforcing_consecutive_5xx'?: (_google_protobuf_UInt32Value | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through success rate statistics. This setting can be used to + * disable ejection or to ramp it up slowly. Defaults to 100. + */ + 'enforcing_success_rate'?: (_google_protobuf_UInt32Value | null); + /** + * The number of hosts in a cluster that must have enough request volume to + * detect success rate outliers. If the number of hosts is less than this + * setting, outlier detection via success rate statistics is not performed + * for any host in the cluster. Defaults to 5. + */ + 'success_rate_minimum_hosts'?: (_google_protobuf_UInt32Value | null); + /** + * The minimum number of total requests that must be collected in one + * interval (as defined by the interval duration above) to include this host + * in success rate based outlier detection. If the volume is lower than this + * setting, outlier detection via success rate statistics is not performed + * for that host. Defaults to 100. + */ + 'success_rate_request_volume'?: (_google_protobuf_UInt32Value | null); + /** + * This factor is used to determine the ejection threshold for success rate + * outlier ejection. The ejection threshold is the difference between the + * mean success rate, and the product of this factor and the standard + * deviation of the mean success rate: mean - (stdev * + * success_rate_stdev_factor). This factor is divided by a thousand to get a + * double. That is, if the desired factor is 1.9, the runtime value should + * be 1900. Defaults to 1900. + */ + 'success_rate_stdev_factor'?: (_google_protobuf_UInt32Value | null); + /** + * The number of consecutive gateway failures (502, 503, 504 status codes) + * before a consecutive gateway failure ejection occurs. Defaults to 5. + */ + 'consecutive_gateway_failure'?: (_google_protobuf_UInt32Value | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through consecutive gateway failures. This setting can be + * used to disable ejection or to ramp it up slowly. Defaults to 0. + */ + 'enforcing_consecutive_gateway_failure'?: (_google_protobuf_UInt32Value | null); + /** + * Determines whether to distinguish local origin failures from external errors. If set to true + * the following configuration parameters are taken into account: + * :ref:`consecutive_local_origin_failure`, + * :ref:`enforcing_consecutive_local_origin_failure` + * and + * :ref:`enforcing_local_origin_success_rate`. + * Defaults to false. + */ + 'split_external_local_origin_errors'?: (boolean); + /** + * The number of consecutive locally originated failures before ejection + * occurs. Defaults to 5. Parameter takes effect only when + * :ref:`split_external_local_origin_errors` + * is set to true. + */ + 'consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through consecutive locally originated failures. This setting can be + * used to disable ejection or to ramp it up slowly. Defaults to 100. + * Parameter takes effect only when + * :ref:`split_external_local_origin_errors` + * is set to true. + */ + 'enforcing_consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through success rate statistics for locally originated errors. + * This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. + * Parameter takes effect only when + * :ref:`split_external_local_origin_errors` + * is set to true. + */ + 'enforcing_local_origin_success_rate'?: (_google_protobuf_UInt32Value | null); + /** + * The failure percentage to use when determining failure percentage-based outlier detection. If + * the failure percentage of a given host is greater than or equal to this value, it will be + * ejected. Defaults to 85. + */ + 'failure_percentage_threshold'?: (_google_protobuf_UInt32Value | null); + /** + * The % chance that a host will be actually ejected when an outlier status is detected through + * failure percentage statistics. This setting can be used to disable ejection or to ramp it up + * slowly. Defaults to 0. + * + * [#next-major-version: setting this without setting failure_percentage_threshold should be + * invalid in v4.] + */ + 'enforcing_failure_percentage'?: (_google_protobuf_UInt32Value | null); + /** + * The % chance that a host will be actually ejected when an outlier status is detected through + * local-origin failure percentage statistics. This setting can be used to disable ejection or to + * ramp it up slowly. Defaults to 0. + */ + 'enforcing_failure_percentage_local_origin'?: (_google_protobuf_UInt32Value | null); + /** + * The minimum number of hosts in a cluster in order to perform failure percentage-based ejection. + * If the total number of hosts in the cluster is less than this value, failure percentage-based + * ejection will not be performed. Defaults to 5. + */ + 'failure_percentage_minimum_hosts'?: (_google_protobuf_UInt32Value | null); + /** + * The minimum number of total requests that must be collected in one interval (as defined by the + * interval duration above) to perform failure percentage-based ejection for this host. If the + * volume is lower than this setting, failure percentage-based ejection will not be performed for + * this host. Defaults to 50. + */ + 'failure_percentage_request_volume'?: (_google_protobuf_UInt32Value | null); + /** + * The maximum time that a host is ejected for. See :ref:`base_ejection_time` + * for more information. If not specified, the default value (300000ms or 300s) or + * :ref:`base_ejection_time` value is applied, whatever is larger. + */ + 'max_ejection_time'?: (_google_protobuf_Duration | null); +} + +/** + * See the :ref:`architecture overview ` for + * more information on outlier detection. + * [#next-free-field: 22] + */ +export interface OutlierDetection__Output { + /** + * The number of consecutive 5xx responses or local origin errors that are mapped + * to 5xx error codes before a consecutive 5xx ejection + * occurs. Defaults to 5. + */ + 'consecutive_5xx': (_google_protobuf_UInt32Value__Output | null); + /** + * The time interval between ejection analysis sweeps. This can result in + * both new ejections as well as hosts being returned to service. Defaults + * to 10000ms or 10s. + */ + 'interval': (_google_protobuf_Duration__Output | null); + /** + * The base time that a host is ejected for. The real time is equal to the + * base time multiplied by the number of times the host has been ejected and is + * capped by :ref:`max_ejection_time`. + * Defaults to 30000ms or 30s. + */ + 'base_ejection_time': (_google_protobuf_Duration__Output | null); + /** + * The maximum % of an upstream cluster that can be ejected due to outlier + * detection. Defaults to 10% but will eject at least one host regardless of the value. + */ + 'max_ejection_percent': (_google_protobuf_UInt32Value__Output | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through consecutive 5xx. This setting can be used to disable + * ejection or to ramp it up slowly. Defaults to 100. + */ + 'enforcing_consecutive_5xx': (_google_protobuf_UInt32Value__Output | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through success rate statistics. This setting can be used to + * disable ejection or to ramp it up slowly. Defaults to 100. + */ + 'enforcing_success_rate': (_google_protobuf_UInt32Value__Output | null); + /** + * The number of hosts in a cluster that must have enough request volume to + * detect success rate outliers. If the number of hosts is less than this + * setting, outlier detection via success rate statistics is not performed + * for any host in the cluster. Defaults to 5. + */ + 'success_rate_minimum_hosts': (_google_protobuf_UInt32Value__Output | null); + /** + * The minimum number of total requests that must be collected in one + * interval (as defined by the interval duration above) to include this host + * in success rate based outlier detection. If the volume is lower than this + * setting, outlier detection via success rate statistics is not performed + * for that host. Defaults to 100. + */ + 'success_rate_request_volume': (_google_protobuf_UInt32Value__Output | null); + /** + * This factor is used to determine the ejection threshold for success rate + * outlier ejection. The ejection threshold is the difference between the + * mean success rate, and the product of this factor and the standard + * deviation of the mean success rate: mean - (stdev * + * success_rate_stdev_factor). This factor is divided by a thousand to get a + * double. That is, if the desired factor is 1.9, the runtime value should + * be 1900. Defaults to 1900. + */ + 'success_rate_stdev_factor': (_google_protobuf_UInt32Value__Output | null); + /** + * The number of consecutive gateway failures (502, 503, 504 status codes) + * before a consecutive gateway failure ejection occurs. Defaults to 5. + */ + 'consecutive_gateway_failure': (_google_protobuf_UInt32Value__Output | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through consecutive gateway failures. This setting can be + * used to disable ejection or to ramp it up slowly. Defaults to 0. + */ + 'enforcing_consecutive_gateway_failure': (_google_protobuf_UInt32Value__Output | null); + /** + * Determines whether to distinguish local origin failures from external errors. If set to true + * the following configuration parameters are taken into account: + * :ref:`consecutive_local_origin_failure`, + * :ref:`enforcing_consecutive_local_origin_failure` + * and + * :ref:`enforcing_local_origin_success_rate`. + * Defaults to false. + */ + 'split_external_local_origin_errors': (boolean); + /** + * The number of consecutive locally originated failures before ejection + * occurs. Defaults to 5. Parameter takes effect only when + * :ref:`split_external_local_origin_errors` + * is set to true. + */ + 'consecutive_local_origin_failure': (_google_protobuf_UInt32Value__Output | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through consecutive locally originated failures. This setting can be + * used to disable ejection or to ramp it up slowly. Defaults to 100. + * Parameter takes effect only when + * :ref:`split_external_local_origin_errors` + * is set to true. + */ + 'enforcing_consecutive_local_origin_failure': (_google_protobuf_UInt32Value__Output | null); + /** + * The % chance that a host will be actually ejected when an outlier status + * is detected through success rate statistics for locally originated errors. + * This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. + * Parameter takes effect only when + * :ref:`split_external_local_origin_errors` + * is set to true. + */ + 'enforcing_local_origin_success_rate': (_google_protobuf_UInt32Value__Output | null); + /** + * The failure percentage to use when determining failure percentage-based outlier detection. If + * the failure percentage of a given host is greater than or equal to this value, it will be + * ejected. Defaults to 85. + */ + 'failure_percentage_threshold': (_google_protobuf_UInt32Value__Output | null); + /** + * The % chance that a host will be actually ejected when an outlier status is detected through + * failure percentage statistics. This setting can be used to disable ejection or to ramp it up + * slowly. Defaults to 0. + * + * [#next-major-version: setting this without setting failure_percentage_threshold should be + * invalid in v4.] + */ + 'enforcing_failure_percentage': (_google_protobuf_UInt32Value__Output | null); + /** + * The % chance that a host will be actually ejected when an outlier status is detected through + * local-origin failure percentage statistics. This setting can be used to disable ejection or to + * ramp it up slowly. Defaults to 0. + */ + 'enforcing_failure_percentage_local_origin': (_google_protobuf_UInt32Value__Output | null); + /** + * The minimum number of hosts in a cluster in order to perform failure percentage-based ejection. + * If the total number of hosts in the cluster is less than this value, failure percentage-based + * ejection will not be performed. Defaults to 5. + */ + 'failure_percentage_minimum_hosts': (_google_protobuf_UInt32Value__Output | null); + /** + * The minimum number of total requests that must be collected in one interval (as defined by the + * interval duration above) to perform failure percentage-based ejection for this host. If the + * volume is lower than this setting, failure percentage-based ejection will not be performed for + * this host. Defaults to 50. + */ + 'failure_percentage_request_volume': (_google_protobuf_UInt32Value__Output | null); + /** + * The maximum time that a host is ejected for. See :ref:`base_ejection_time` + * for more information. If not specified, the default value (300000ms or 300s) or + * :ref:`base_ejection_time` value is applied, whatever is larger. + */ + 'max_ejection_time': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/TrackClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/TrackClusterStats.ts new file mode 100644 index 000000000..a65d1fd8d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/TrackClusterStats.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + + +export interface TrackClusterStats { + /** + * If timeout_budgets is true, the :ref:`timeout budget histograms + * ` will be published for each + * request. These show what percentage of a request's per try and global timeout was used. A value + * of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value + * of 100 would indicate that the request took the entirety of the timeout given to it. + */ + 'timeout_budgets'?: (boolean); + /** + * If request_response_sizes is true, then the :ref:`histograms + * ` tracking header and body sizes + * of requests and responses will be published. + */ + 'request_response_sizes'?: (boolean); +} + +export interface TrackClusterStats__Output { + /** + * If timeout_budgets is true, the :ref:`timeout budget histograms + * ` will be published for each + * request. These show what percentage of a request's per try and global timeout was used. A value + * of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value + * of 100 would indicate that the request took the entirety of the timeout given to it. + */ + 'timeout_budgets': (boolean); + /** + * If request_response_sizes is true, then the :ref:`histograms + * ` tracking header and body sizes + * of requests and responses will be published. + */ + 'request_response_sizes': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts new file mode 100644 index 000000000..f2dbd0608 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; + +/** + * An extensible structure containing the address Envoy should bind to when + * establishing upstream connections. + */ +export interface UpstreamBindConfig { + /** + * The address Envoy should bind to when establishing upstream connections. + */ + 'source_address'?: (_envoy_config_core_v3_Address | null); +} + +/** + * An extensible structure containing the address Envoy should bind to when + * establishing upstream connections. + */ +export interface UpstreamBindConfig__Output { + /** + * The address Envoy should bind to when establishing upstream connections. + */ + 'source_address': (_envoy_config_core_v3_Address__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts new file mode 100644 index 000000000..483d1072d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +import type { TcpKeepalive as _envoy_config_core_v3_TcpKeepalive, TcpKeepalive__Output as _envoy_config_core_v3_TcpKeepalive__Output } from '../../../../envoy/config/core/v3/TcpKeepalive'; + +export interface UpstreamConnectionOptions { + /** + * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. + */ + 'tcp_keepalive'?: (_envoy_config_core_v3_TcpKeepalive | null); +} + +export interface UpstreamConnectionOptions__Output { + /** + * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. + */ + 'tcp_keepalive': (_envoy_config_core_v3_TcpKeepalive__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts new file mode 100644 index 000000000..32c483344 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { SocketAddress as _envoy_config_core_v3_SocketAddress, SocketAddress__Output as _envoy_config_core_v3_SocketAddress__Output } from '../../../../envoy/config/core/v3/SocketAddress'; +import type { Pipe as _envoy_config_core_v3_Pipe, Pipe__Output as _envoy_config_core_v3_Pipe__Output } from '../../../../envoy/config/core/v3/Pipe'; +import type { EnvoyInternalAddress as _envoy_config_core_v3_EnvoyInternalAddress, EnvoyInternalAddress__Output as _envoy_config_core_v3_EnvoyInternalAddress__Output } from '../../../../envoy/config/core/v3/EnvoyInternalAddress'; + +/** + * Addresses specify either a logical or physical address and port, which are + * used to tell Envoy where to bind/listen, connect to upstream and find + * management servers. + */ +export interface Address { + 'socket_address'?: (_envoy_config_core_v3_SocketAddress | null); + 'pipe'?: (_envoy_config_core_v3_Pipe | null); + /** + * [#not-implemented-hide:] + */ + 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress | null); + 'address'?: "socket_address"|"pipe"|"envoy_internal_address"; +} + +/** + * Addresses specify either a logical or physical address and port, which are + * used to tell Envoy where to bind/listen, connect to upstream and find + * management servers. + */ +export interface Address__Output { + 'socket_address'?: (_envoy_config_core_v3_SocketAddress__Output | null); + 'pipe'?: (_envoy_config_core_v3_Pipe__Output | null); + /** + * [#not-implemented-hide:] + */ + 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress__Output | null); + 'address': "socket_address"|"pipe"|"envoy_internal_address"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts new file mode 100644 index 000000000..428ab4b56 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts @@ -0,0 +1,18 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + + +/** + * Aggregated Discovery Service (ADS) options. This is currently empty, but when + * set in :ref:`ConfigSource ` can be used to + * specify that ADS is to be used. + */ +export interface AggregatedConfigSource { +} + +/** + * Aggregated Discovery Service (ADS) options. This is currently empty, but when + * set in :ref:`ConfigSource ` can be used to + * specify that ADS is to be used. + */ +export interface AggregatedConfigSource__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts new file mode 100644 index 000000000..6fb167174 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts @@ -0,0 +1,72 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Configures the alternate protocols cache which tracks alternate protocols that can be used to + * make an HTTP connection to an origin server. See https://tools.ietf.org/html/rfc7838 for + * HTTP Alternative Services and https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 + * for the "HTTPS" DNS resource record. + */ +export interface AlternateProtocolsCacheOptions { + /** + * The name of the cache. Multiple named caches allow independent alternate protocols cache + * configurations to operate within a single Envoy process using different configurations. All + * alternate protocols cache options with the same name *must* be equal in all fields when + * referenced from different configuration components. Configuration will fail to load if this is + * not the case. + */ + 'name'?: (string); + /** + * The maximum number of entries that the cache will hold. If not specified defaults to 1024. + * + * .. note: + * + * The implementation is approximate and enforced independently on each worker thread, thus + * it is possible for the maximum entries in the cache to go slightly above the configured + * value depending on timing. This is similar to how other circuit breakers work. + */ + 'max_entries'?: (_google_protobuf_UInt32Value | null); + /** + * Allows configuring a persistent + * :ref:`key value store ` to flush + * alternate protocols entries to disk. + * This function is currently only supported if concurrency is 1 + */ + 'key_value_store_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +/** + * Configures the alternate protocols cache which tracks alternate protocols that can be used to + * make an HTTP connection to an origin server. See https://tools.ietf.org/html/rfc7838 for + * HTTP Alternative Services and https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 + * for the "HTTPS" DNS resource record. + */ +export interface AlternateProtocolsCacheOptions__Output { + /** + * The name of the cache. Multiple named caches allow independent alternate protocols cache + * configurations to operate within a single Envoy process using different configurations. All + * alternate protocols cache options with the same name *must* be equal in all fields when + * referenced from different configuration components. Configuration will fail to load if this is + * not the case. + */ + 'name': (string); + /** + * The maximum number of entries that the cache will hold. If not specified defaults to 1024. + * + * .. note: + * + * The implementation is approximate and enforced independently on each worker thread, thus + * it is possible for the maximum entries in the cache to go slightly above the configured + * value depending on timing. This is similar to how other circuit breakers work. + */ + 'max_entries': (_google_protobuf_UInt32Value__Output | null); + /** + * Allows configuring a persistent + * :ref:`key value store ` to flush + * alternate protocols entries to disk. + * This function is currently only supported if concurrency is 1 + */ + 'key_value_store_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts new file mode 100644 index 000000000..b303a08d8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts @@ -0,0 +1,147 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { GrpcService as _envoy_config_core_v3_GrpcService, GrpcService__Output as _envoy_config_core_v3_GrpcService__Output } from '../../../../envoy/config/core/v3/GrpcService'; +import type { RateLimitSettings as _envoy_config_core_v3_RateLimitSettings, RateLimitSettings__Output as _envoy_config_core_v3_RateLimitSettings__Output } from '../../../../envoy/config/core/v3/RateLimitSettings'; +import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; + +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + +/** + * APIs may be fetched via either REST or gRPC. + */ +export enum _envoy_config_core_v3_ApiConfigSource_ApiType { + /** + * Ideally this would be 'reserved 0' but one can't reserve the default + * value. Instead we throw an exception if this is ever used. + */ + DEPRECATED_AND_UNAVAILABLE_DO_NOT_USE = 0, + /** + * REST-JSON v2 API. The `canonical JSON encoding + * `_ for + * the v2 protos is used. + */ + REST = 1, + /** + * SotW gRPC service. + */ + GRPC = 2, + /** + * Using the delta xDS gRPC service, i.e. DeltaDiscovery{Request,Response} + * rather than Discovery{Request,Response}. Rather than sending Envoy the entire state + * with every update, the xDS server only sends what has changed since the last update. + */ + DELTA_GRPC = 3, + /** + * SotW xDS gRPC with ADS. All resources which resolve to this configuration source will be + * multiplexed on a single connection to an ADS endpoint. + * [#not-implemented-hide:] + */ + AGGREGATED_GRPC = 5, + /** + * Delta xDS gRPC with ADS. All resources which resolve to this configuration source will be + * multiplexed on a single connection to an ADS endpoint. + * [#not-implemented-hide:] + */ + AGGREGATED_DELTA_GRPC = 6, +} + +/** + * API configuration source. This identifies the API type and cluster that Envoy + * will use to fetch an xDS API. + * [#next-free-field: 9] + */ +export interface ApiConfigSource { + /** + * API type (gRPC, REST, delta gRPC) + */ + 'api_type'?: (_envoy_config_core_v3_ApiConfigSource_ApiType | keyof typeof _envoy_config_core_v3_ApiConfigSource_ApiType); + /** + * Cluster names should be used only with REST. If > 1 + * cluster is defined, clusters will be cycled through if any kind of failure + * occurs. + * + * .. note:: + * + * The cluster with name ``cluster_name`` must be statically defined and its + * type must not be ``EDS``. + */ + 'cluster_names'?: (string)[]; + /** + * For REST APIs, the delay between successive polls. + */ + 'refresh_delay'?: (_google_protobuf_Duration | null); + /** + * Multiple gRPC services be provided for GRPC. If > 1 cluster is defined, + * services will be cycled through if any kind of failure occurs. + */ + 'grpc_services'?: (_envoy_config_core_v3_GrpcService)[]; + /** + * For REST APIs, the request timeout. If not set, a default value of 1s will be used. + */ + 'request_timeout'?: (_google_protobuf_Duration | null); + /** + * For GRPC APIs, the rate limit settings. If present, discovery requests made by Envoy will be + * rate limited. + */ + 'rate_limit_settings'?: (_envoy_config_core_v3_RateLimitSettings | null); + /** + * Skip the node identifier in subsequent discovery requests for streaming gRPC config types. + */ + 'set_node_on_first_message_only'?: (boolean); + /** + * API version for xDS transport protocol. This describes the xDS gRPC/REST + * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + */ + 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); +} + +/** + * API configuration source. This identifies the API type and cluster that Envoy + * will use to fetch an xDS API. + * [#next-free-field: 9] + */ +export interface ApiConfigSource__Output { + /** + * API type (gRPC, REST, delta gRPC) + */ + 'api_type': (keyof typeof _envoy_config_core_v3_ApiConfigSource_ApiType); + /** + * Cluster names should be used only with REST. If > 1 + * cluster is defined, clusters will be cycled through if any kind of failure + * occurs. + * + * .. note:: + * + * The cluster with name ``cluster_name`` must be statically defined and its + * type must not be ``EDS``. + */ + 'cluster_names': (string)[]; + /** + * For REST APIs, the delay between successive polls. + */ + 'refresh_delay': (_google_protobuf_Duration__Output | null); + /** + * Multiple gRPC services be provided for GRPC. If > 1 cluster is defined, + * services will be cycled through if any kind of failure occurs. + */ + 'grpc_services': (_envoy_config_core_v3_GrpcService__Output)[]; + /** + * For REST APIs, the request timeout. If not set, a default value of 1s will be used. + */ + 'request_timeout': (_google_protobuf_Duration__Output | null); + /** + * For GRPC APIs, the rate limit settings. If present, discovery requests made by Envoy will be + * rate limited. + */ + 'rate_limit_settings': (_envoy_config_core_v3_RateLimitSettings__Output | null); + /** + * Skip the node identifier in subsequent discovery requests for streaming gRPC config types. + */ + 'set_node_on_first_message_only': (boolean); + /** + * API version for xDS transport protocol. This describes the xDS gRPC/REST + * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + */ + 'transport_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts new file mode 100644 index 000000000..b46f6ec43 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts @@ -0,0 +1,22 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + +/** + * xDS API and non-xDS services version. This is used to describe both resource and transport + * protocol versions (in distinct configuration fields). + */ +export enum ApiVersion { + /** + * When not specified, we assume v2, to ease migration to Envoy's stable API + * versioning. If a client does not support v2 (e.g. due to deprecation), this + * is an invalid value. + */ + AUTO = 0, + /** + * Use xDS v2 API. + */ + V2 = 1, + /** + * Use xDS v3 API. + */ + V3 = 2, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts new file mode 100644 index 000000000..aa152155b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; +import type { RemoteDataSource as _envoy_config_core_v3_RemoteDataSource, RemoteDataSource__Output as _envoy_config_core_v3_RemoteDataSource__Output } from '../../../../envoy/config/core/v3/RemoteDataSource'; + +/** + * Async data source which support async data fetch. + */ +export interface AsyncDataSource { + /** + * Local async data source. + */ + 'local'?: (_envoy_config_core_v3_DataSource | null); + /** + * Remote async data source. + */ + 'remote'?: (_envoy_config_core_v3_RemoteDataSource | null); + 'specifier'?: "local"|"remote"; +} + +/** + * Async data source which support async data fetch. + */ +export interface AsyncDataSource__Output { + /** + * Local async data source. + */ + 'local'?: (_envoy_config_core_v3_DataSource__Output | null); + /** + * Remote async data source. + */ + 'remote'?: (_envoy_config_core_v3_RemoteDataSource__Output | null); + 'specifier': "local"|"remote"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts new file mode 100644 index 000000000..1049dec0c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/backoff.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * Configuration defining a jittered exponential back off strategy. + */ +export interface BackoffStrategy { + /** + * The base interval to be used for the next back off computation. It should + * be greater than zero and less than or equal to :ref:`max_interval + * `. + */ + 'base_interval'?: (_google_protobuf_Duration | null); + /** + * Specifies the maximum interval between retries. This parameter is optional, + * but must be greater than or equal to the :ref:`base_interval + * ` if set. The default + * is 10 times the :ref:`base_interval + * `. + */ + 'max_interval'?: (_google_protobuf_Duration | null); +} + +/** + * Configuration defining a jittered exponential back off strategy. + */ +export interface BackoffStrategy__Output { + /** + * The base interval to be used for the next back off computation. It should + * be greater than zero and less than or equal to :ref:`max_interval + * `. + */ + 'base_interval': (_google_protobuf_Duration__Output | null); + /** + * Specifies the maximum interval between retries. This parameter is optional, + * but must be greater than or equal to the :ref:`base_interval + * ` if set. The default + * is 10 times the :ref:`base_interval + * `. + */ + 'max_interval': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts new file mode 100644 index 000000000..d3b2cc584 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts @@ -0,0 +1,49 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { SocketAddress as _envoy_config_core_v3_SocketAddress, SocketAddress__Output as _envoy_config_core_v3_SocketAddress__Output } from '../../../../envoy/config/core/v3/SocketAddress'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; + +export interface BindConfig { + /** + * The address to bind to when creating a socket. + */ + 'source_address'?: (_envoy_config_core_v3_SocketAddress | null); + /** + * Whether to set the *IP_FREEBIND* option when creating the socket. When this + * flag is set to true, allows the :ref:`source_address + * ` to be an IP address + * that is not configured on the system running Envoy. When this flag is set + * to false, the option *IP_FREEBIND* is disabled on the socket. When this + * flag is not set (default), the socket is not modified, i.e. the option is + * neither enabled nor disabled. + */ + 'freebind'?: (_google_protobuf_BoolValue | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; +} + +export interface BindConfig__Output { + /** + * The address to bind to when creating a socket. + */ + 'source_address': (_envoy_config_core_v3_SocketAddress__Output | null); + /** + * Whether to set the *IP_FREEBIND* option when creating the socket. When this + * flag is set to true, allows the :ref:`source_address + * ` to be an IP address + * that is not configured on the system running Envoy. When this flag is set + * to false, the option *IP_FREEBIND* is disabled on the socket. When this + * flag is not set (default), the socket is not modified, i.e. the option is + * neither enabled nor disabled. + */ + 'freebind': (_google_protobuf_BoolValue__Output | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts new file mode 100644 index 000000000..1a86e918e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { SemanticVersion as _envoy_type_v3_SemanticVersion, SemanticVersion__Output as _envoy_type_v3_SemanticVersion__Output } from '../../../../envoy/type/v3/SemanticVersion'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; + +/** + * BuildVersion combines SemVer version of extension with free-form build information + * (i.e. 'alpha', 'private-build') as a set of strings. + */ +export interface BuildVersion { + /** + * SemVer version of extension. + */ + 'version'?: (_envoy_type_v3_SemanticVersion | null); + /** + * Free-form build information. + * Envoy defines several well known keys in the source/common/version/version.h file + */ + 'metadata'?: (_google_protobuf_Struct | null); +} + +/** + * BuildVersion combines SemVer version of extension with free-form build information + * (i.e. 'alpha', 'private-build') as a set of strings. + */ +export interface BuildVersion__Output { + /** + * SemVer version of extension. + */ + 'version': (_envoy_type_v3_SemanticVersion__Output | null); + /** + * Free-form build information. + * Envoy defines several well known keys in the source/common/version/version.h file + */ + 'metadata': (_google_protobuf_Struct__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts new file mode 100644 index 000000000..4e01fa354 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts @@ -0,0 +1,33 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +/** + * CidrRange specifies an IP Address and a prefix length to construct + * the subnet mask for a `CIDR `_ range. + */ +export interface CidrRange { + /** + * IPv4 or IPv6 address, e.g. ``192.0.0.0`` or ``2001:db8::``. + */ + 'address_prefix'?: (string); + /** + * Length of prefix, e.g. 0, 32. Defaults to 0 when unset. + */ + 'prefix_len'?: (_google_protobuf_UInt32Value | null); +} + +/** + * CidrRange specifies an IP Address and a prefix length to construct + * the subnet mask for a `CIDR `_ range. + */ +export interface CidrRange__Output { + /** + * IPv4 or IPv6 address, e.g. ``192.0.0.0`` or ``2001:db8::``. + */ + 'address_prefix': (string); + /** + * Length of prefix, e.g. 0, 32. Defaults to 0 when unset. + */ + 'prefix_len': (_google_protobuf_UInt32Value__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts new file mode 100644 index 000000000..a39c0f077 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts @@ -0,0 +1,158 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + +import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfigSource__Output as _envoy_config_core_v3_ApiConfigSource__Output } from '../../../../envoy/config/core/v3/ApiConfigSource'; +import type { AggregatedConfigSource as _envoy_config_core_v3_AggregatedConfigSource, AggregatedConfigSource__Output as _envoy_config_core_v3_AggregatedConfigSource__Output } from '../../../../envoy/config/core/v3/AggregatedConfigSource'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { SelfConfigSource as _envoy_config_core_v3_SelfConfigSource, SelfConfigSource__Output as _envoy_config_core_v3_SelfConfigSource__Output } from '../../../../envoy/config/core/v3/SelfConfigSource'; +import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; +import type { Authority as _xds_core_v3_Authority, Authority__Output as _xds_core_v3_Authority__Output } from '../../../../xds/core/v3/Authority'; + +/** + * Configuration for :ref:`listeners `, :ref:`clusters + * `, :ref:`routes + * `, :ref:`endpoints + * ` etc. may either be sourced from the + * filesystem or from an xDS API source. Filesystem configs are watched with + * inotify for updates. + * [#next-free-field: 8] + */ +export interface ConfigSource { + /** + * Path on the filesystem to source and watch for configuration updates. + * When sourcing configuration for :ref:`secret `, + * the certificate and key files are also watched for updates. + * + * .. note:: + * + * The path to the source must exist at config load time. + * + * .. note:: + * + * Envoy will only watch the file path for *moves.* This is because in general only moves + * are atomic. The same method of swapping files as is demonstrated in the + * :ref:`runtime documentation ` can be used here also. + */ + 'path'?: (string); + /** + * API configuration source. + */ + 'api_config_source'?: (_envoy_config_core_v3_ApiConfigSource | null); + /** + * When set, ADS will be used to fetch resources. The ADS API configuration + * source in the bootstrap configuration is used. + */ + 'ads'?: (_envoy_config_core_v3_AggregatedConfigSource | null); + /** + * When this timeout is specified, Envoy will wait no longer than the specified time for first + * config response on this xDS subscription during the :ref:`initialization process + * `. After reaching the timeout, Envoy will move to the next + * initialization phase, even if the first config is not delivered yet. The timer is activated + * when the xDS API subscription starts, and is disarmed on first config update or on error. 0 + * means no timeout - Envoy will wait indefinitely for the first xDS config (unless another + * timeout applies). The default is 15s. + */ + 'initial_fetch_timeout'?: (_google_protobuf_Duration | null); + /** + * [#not-implemented-hide:] + * When set, the client will access the resources from the same server it got the + * ConfigSource from, although not necessarily from the same stream. This is similar to the + * :ref:`ads` field, except that the client may use a + * different stream to the same server. As a result, this field can be used for things + * like LRS that cannot be sent on an ADS stream. It can also be used to link from (e.g.) + * LDS to RDS on the same server without requiring the management server to know its name + * or required credentials. + * [#next-major-version: In xDS v3, consider replacing the ads field with this one, since + * this field can implicitly mean to use the same stream in the case where the ConfigSource + * is provided via ADS and the specified data can also be obtained via ADS.] + */ + 'self'?: (_envoy_config_core_v3_SelfConfigSource | null); + /** + * API version for xDS resources. This implies the type URLs that the client + * will request for resources and the resource type that the client will in + * turn expect to be delivered. + */ + 'resource_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); + /** + * Authorities that this config source may be used for. An authority specified in a xdstp:// URL + * is resolved to a *ConfigSource* prior to configuration fetch. This field provides the + * association between authority name and configuration source. + * [#not-implemented-hide:] + */ + 'authorities'?: (_xds_core_v3_Authority)[]; + 'config_source_specifier'?: "path"|"api_config_source"|"ads"|"self"; +} + +/** + * Configuration for :ref:`listeners `, :ref:`clusters + * `, :ref:`routes + * `, :ref:`endpoints + * ` etc. may either be sourced from the + * filesystem or from an xDS API source. Filesystem configs are watched with + * inotify for updates. + * [#next-free-field: 8] + */ +export interface ConfigSource__Output { + /** + * Path on the filesystem to source and watch for configuration updates. + * When sourcing configuration for :ref:`secret `, + * the certificate and key files are also watched for updates. + * + * .. note:: + * + * The path to the source must exist at config load time. + * + * .. note:: + * + * Envoy will only watch the file path for *moves.* This is because in general only moves + * are atomic. The same method of swapping files as is demonstrated in the + * :ref:`runtime documentation ` can be used here also. + */ + 'path'?: (string); + /** + * API configuration source. + */ + 'api_config_source'?: (_envoy_config_core_v3_ApiConfigSource__Output | null); + /** + * When set, ADS will be used to fetch resources. The ADS API configuration + * source in the bootstrap configuration is used. + */ + 'ads'?: (_envoy_config_core_v3_AggregatedConfigSource__Output | null); + /** + * When this timeout is specified, Envoy will wait no longer than the specified time for first + * config response on this xDS subscription during the :ref:`initialization process + * `. After reaching the timeout, Envoy will move to the next + * initialization phase, even if the first config is not delivered yet. The timer is activated + * when the xDS API subscription starts, and is disarmed on first config update or on error. 0 + * means no timeout - Envoy will wait indefinitely for the first xDS config (unless another + * timeout applies). The default is 15s. + */ + 'initial_fetch_timeout': (_google_protobuf_Duration__Output | null); + /** + * [#not-implemented-hide:] + * When set, the client will access the resources from the same server it got the + * ConfigSource from, although not necessarily from the same stream. This is similar to the + * :ref:`ads` field, except that the client may use a + * different stream to the same server. As a result, this field can be used for things + * like LRS that cannot be sent on an ADS stream. It can also be used to link from (e.g.) + * LDS to RDS on the same server without requiring the management server to know its name + * or required credentials. + * [#next-major-version: In xDS v3, consider replacing the ads field with this one, since + * this field can implicitly mean to use the same stream in the case where the ConfigSource + * is provided via ADS and the specified data can also be obtained via ADS.] + */ + 'self'?: (_envoy_config_core_v3_SelfConfigSource__Output | null); + /** + * API version for xDS resources. This implies the type URLs that the client + * will request for resources and the resource type that the client will in + * turn expect to be delivered. + */ + 'resource_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); + /** + * Authorities that this config source may be used for. An authority specified in a xdstp:// URL + * is resolved to a *ConfigSource* prior to configuration fetch. This field provides the + * association between authority name and configuration source. + * [#not-implemented-hide:] + */ + 'authorities': (_xds_core_v3_Authority__Output)[]; + 'config_source_specifier': "path"|"api_config_source"|"ads"|"self"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ControlPlane.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ControlPlane.ts new file mode 100644 index 000000000..dc55f8042 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ControlPlane.ts @@ -0,0 +1,26 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Identifies a specific ControlPlane instance that Envoy is connected to. + */ +export interface ControlPlane { + /** + * An opaque control plane identifier that uniquely identifies an instance + * of control plane. This can be used to identify which control plane instance, + * the Envoy is connected to. + */ + 'identifier'?: (string); +} + +/** + * Identifies a specific ControlPlane instance that Envoy is connected to. + */ +export interface ControlPlane__Output { + /** + * An opaque control plane identifier that uniquely identifies an instance + * of control plane. This can be used to identify which control plane instance, + * the Envoy is connected to. + */ + 'identifier': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts new file mode 100644 index 000000000..cc29e084f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts @@ -0,0 +1,40 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Data source consisting of either a file or an inline value. + */ +export interface DataSource { + /** + * Local filesystem data source. + */ + 'filename'?: (string); + /** + * Bytes inlined in the configuration. + */ + 'inline_bytes'?: (Buffer | Uint8Array | string); + /** + * String inlined in the configuration. + */ + 'inline_string'?: (string); + 'specifier'?: "filename"|"inline_bytes"|"inline_string"; +} + +/** + * Data source consisting of either a file or an inline value. + */ +export interface DataSource__Output { + /** + * Local filesystem data source. + */ + 'filename'?: (string); + /** + * Bytes inlined in the configuration. + */ + 'inline_bytes'?: (Buffer); + /** + * String inlined in the configuration. + */ + 'inline_string'?: (string); + 'specifier': "filename"|"inline_bytes"|"inline_string"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts new file mode 100644 index 000000000..c87be12e2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts @@ -0,0 +1,42 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/resolver.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { DnsResolverOptions as _envoy_config_core_v3_DnsResolverOptions, DnsResolverOptions__Output as _envoy_config_core_v3_DnsResolverOptions__Output } from '../../../../envoy/config/core/v3/DnsResolverOptions'; + +/** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + */ +export interface DnsResolutionConfig { + /** + * A list of dns resolver addresses. If specified, the DNS client library will perform resolution + * via the underlying DNS resolvers. Otherwise, the default system resolvers + * (e.g., /etc/resolv.conf) will be used. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only allows overriding DNS resolvers via system settings. + */ + 'resolvers'?: (_envoy_config_core_v3_Address)[]; + /** + * Configuration of DNS resolver option flags which control the behavior of the DNS resolver. + */ + 'dns_resolver_options'?: (_envoy_config_core_v3_DnsResolverOptions | null); +} + +/** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + */ +export interface DnsResolutionConfig__Output { + /** + * A list of dns resolver addresses. If specified, the DNS client library will perform resolution + * via the underlying DNS resolvers. Otherwise, the default system resolvers + * (e.g., /etc/resolv.conf) will be used. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only allows overriding DNS resolvers via system settings. + */ + 'resolvers': (_envoy_config_core_v3_Address__Output)[]; + /** + * Configuration of DNS resolver option flags which control the behavior of the DNS resolver. + */ + 'dns_resolver_options': (_envoy_config_core_v3_DnsResolverOptions__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts new file mode 100644 index 000000000..11b68b150 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/resolver.proto + + +/** + * Configuration of DNS resolver option flags which control the behavior of the DNS resolver. + */ +export interface DnsResolverOptions { + /** + * Use TCP for all DNS queries instead of the default protocol UDP. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only uses UDP for DNS resolution. + */ + 'use_tcp_for_dns_lookups'?: (boolean); + /** + * Do not use the default search domains; only query hostnames as-is or as aliases. + */ + 'no_default_search_domain'?: (boolean); +} + +/** + * Configuration of DNS resolver option flags which control the behavior of the DNS resolver. + */ +export interface DnsResolverOptions__Output { + /** + * Use TCP for all DNS queries instead of the default protocol UDP. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only uses UDP for DNS resolution. + */ + 'use_tcp_for_dns_lookups': (boolean); + /** + * Do not use the default search domains; only query hostnames as-is or as aliases. + */ + 'no_default_search_domain': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts new file mode 100644 index 000000000..936e433db --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts @@ -0,0 +1,28 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + + +/** + * [#not-implemented-hide:] The address represents an envoy internal listener. + * TODO(lambdai): Make this address available for listener and endpoint. + * TODO(asraa): When address available, remove workaround from test/server/server_fuzz_test.cc:30. + */ +export interface EnvoyInternalAddress { + /** + * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + */ + 'server_listener_name'?: (string); + 'address_name_specifier'?: "server_listener_name"; +} + +/** + * [#not-implemented-hide:] The address represents an envoy internal listener. + * TODO(lambdai): Make this address available for listener and endpoint. + * TODO(asraa): When address available, remove workaround from test/server/server_fuzz_test.cc:30. + */ +export interface EnvoyInternalAddress__Output { + /** + * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + */ + 'server_listener_name'?: (string); + 'address_name_specifier': "server_listener_name"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts new file mode 100644 index 000000000..6226b97c8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts @@ -0,0 +1,27 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/event_service_config.proto + +import type { GrpcService as _envoy_config_core_v3_GrpcService, GrpcService__Output as _envoy_config_core_v3_GrpcService__Output } from '../../../../envoy/config/core/v3/GrpcService'; + +/** + * [#not-implemented-hide:] + * Configuration of the event reporting service endpoint. + */ +export interface EventServiceConfig { + /** + * Specifies the gRPC service that hosts the event reporting service. + */ + 'grpc_service'?: (_envoy_config_core_v3_GrpcService | null); + 'config_source_specifier'?: "grpc_service"; +} + +/** + * [#not-implemented-hide:] + * Configuration of the event reporting service endpoint. + */ +export interface EventServiceConfig__Output { + /** + * Specifies the gRPC service that hosts the event reporting service. + */ + 'grpc_service'?: (_envoy_config_core_v3_GrpcService__Output | null); + 'config_source_specifier': "grpc_service"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts new file mode 100644 index 000000000..5f730bd22 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts @@ -0,0 +1,75 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { BuildVersion as _envoy_config_core_v3_BuildVersion, BuildVersion__Output as _envoy_config_core_v3_BuildVersion__Output } from '../../../../envoy/config/core/v3/BuildVersion'; + +/** + * Version and identification for an Envoy extension. + * [#next-free-field: 6] + */ +export interface Extension { + /** + * This is the name of the Envoy filter as specified in the Envoy + * configuration, e.g. envoy.filters.http.router, com.acme.widget. + */ + 'name'?: (string); + /** + * Category of the extension. + * Extension category names use reverse DNS notation. For instance "envoy.filters.listener" + * for Envoy's built-in listener filters or "com.acme.filters.http" for HTTP filters from + * acme.com vendor. + * [#comment:TODO(yanavlasov): Link to the doc with existing envoy category names.] + */ + 'category'?: (string); + /** + * [#not-implemented-hide:] Type descriptor of extension configuration proto. + * [#comment:TODO(yanavlasov): Link to the doc with existing configuration protos.] + * [#comment:TODO(yanavlasov): Add tests when PR #9391 lands.] + */ + 'type_descriptor'?: (string); + /** + * The version is a property of the extension and maintained independently + * of other extensions and the Envoy API. + * This field is not set when extension did not provide version information. + */ + 'version'?: (_envoy_config_core_v3_BuildVersion | null); + /** + * Indicates that the extension is present but was disabled via dynamic configuration. + */ + 'disabled'?: (boolean); +} + +/** + * Version and identification for an Envoy extension. + * [#next-free-field: 6] + */ +export interface Extension__Output { + /** + * This is the name of the Envoy filter as specified in the Envoy + * configuration, e.g. envoy.filters.http.router, com.acme.widget. + */ + 'name': (string); + /** + * Category of the extension. + * Extension category names use reverse DNS notation. For instance "envoy.filters.listener" + * for Envoy's built-in listener filters or "com.acme.filters.http" for HTTP filters from + * acme.com vendor. + * [#comment:TODO(yanavlasov): Link to the doc with existing envoy category names.] + */ + 'category': (string); + /** + * [#not-implemented-hide:] Type descriptor of extension configuration proto. + * [#comment:TODO(yanavlasov): Link to the doc with existing configuration protos.] + * [#comment:TODO(yanavlasov): Add tests when PR #9391 lands.] + */ + 'type_descriptor': (string); + /** + * The version is a property of the extension and maintained independently + * of other extensions and the Envoy API. + * This field is not set when extension did not provide version information. + */ + 'version': (_envoy_config_core_v3_BuildVersion__Output | null); + /** + * Indicates that the extension is present but was disabled via dynamic configuration. + */ + 'disabled': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts new file mode 100644 index 000000000..805762ef5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts @@ -0,0 +1,72 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/extension.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Configuration source specifier for a late-bound extension configuration. The + * parent resource is warmed until all the initial extension configurations are + * received, unless the flag to apply the default configuration is set. + * Subsequent extension updates are atomic on a per-worker basis. Once an + * extension configuration is applied to a request or a connection, it remains + * constant for the duration of processing. If the initial delivery of the + * extension configuration fails, due to a timeout for example, the optional + * default configuration is applied. Without a default configuration, the + * extension is disabled, until an extension configuration is received. The + * behavior of a disabled extension depends on the context. For example, a + * filter chain with a disabled extension filter rejects all incoming streams. + */ +export interface ExtensionConfigSource { + 'config_source'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * Optional default configuration to use as the initial configuration if + * there is a failure to receive the initial extension configuration or if + * `apply_default_config_without_warming` flag is set. + */ + 'default_config'?: (_google_protobuf_Any | null); + /** + * Use the default config as the initial configuration without warming and + * waiting for the first discovery response. Requires the default configuration + * to be supplied. + */ + 'apply_default_config_without_warming'?: (boolean); + /** + * A set of permitted extension type URLs. Extension configuration updates are rejected + * if they do not match any type URL in the set. + */ + 'type_urls'?: (string)[]; +} + +/** + * Configuration source specifier for a late-bound extension configuration. The + * parent resource is warmed until all the initial extension configurations are + * received, unless the flag to apply the default configuration is set. + * Subsequent extension updates are atomic on a per-worker basis. Once an + * extension configuration is applied to a request or a connection, it remains + * constant for the duration of processing. If the initial delivery of the + * extension configuration fails, due to a timeout for example, the optional + * default configuration is applied. Without a default configuration, the + * extension is disabled, until an extension configuration is received. The + * behavior of a disabled extension depends on the context. For example, a + * filter chain with a disabled extension filter rejects all incoming streams. + */ +export interface ExtensionConfigSource__Output { + 'config_source': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * Optional default configuration to use as the initial configuration if + * there is a failure to receive the initial extension configuration or if + * `apply_default_config_without_warming` flag is set. + */ + 'default_config': (_google_protobuf_Any__Output | null); + /** + * Use the default config as the initial configuration without warming and + * waiting for the first discovery response. Requires the default configuration + * to be supplied. + */ + 'apply_default_config_without_warming': (boolean); + /** + * A set of permitted extension type URLs. Extension configuration updates are rejected + * if they do not match any type URL in the set. + */ + 'type_urls': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts new file mode 100644 index 000000000..1f0376a1c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { Http2ProtocolOptions as _envoy_config_core_v3_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_config_core_v3_Http2ProtocolOptions__Output } from '../../../../envoy/config/core/v3/Http2ProtocolOptions'; + +/** + * [#not-implemented-hide:] + */ +export interface GrpcProtocolOptions { + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); +} + +/** + * [#not-implemented-hide:] + */ +export interface GrpcProtocolOptions__Output { + 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts new file mode 100644 index 000000000..0d81f7340 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts @@ -0,0 +1,558 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/grpc_service.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { HeaderValue as _envoy_config_core_v3_HeaderValue, HeaderValue__Output as _envoy_config_core_v3_HeaderValue__Output } from '../../../../envoy/config/core/v3/HeaderValue'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; +import type { Empty as _google_protobuf_Empty, Empty__Output as _google_protobuf_Empty__Output } from '../../../../google/protobuf/Empty'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Long } from '@grpc/proto-loader'; + +/** + * [#next-free-field: 8] + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials { + /** + * Access token credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#ad3a80da696ffdaea943f0f858d7a360d. + */ + 'access_token'?: (string); + /** + * Google Compute Engine credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 + */ + 'google_compute_engine'?: (_google_protobuf_Empty | null); + /** + * Google refresh token credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a96901c997b91bc6513b08491e0dca37c. + */ + 'google_refresh_token'?: (string); + /** + * Service Account JWT Access credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a92a9f959d6102461f66ee973d8e9d3aa. + */ + 'service_account_jwt_access'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials | null); + /** + * Google IAM credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a9fc1fc101b41e680d47028166e76f9d0. + */ + 'google_iam'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials | null); + /** + * Custom authenticator credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a823c6a4b19ffc71fb33e90154ee2ad07. + * https://grpc.io/docs/guides/auth.html#extending-grpc-to-support-other-authentication-mechanisms. + */ + 'from_plugin'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin | null); + /** + * Custom security token service which implements OAuth 2.0 token exchange. + * https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16 + * See https://github.com/grpc/grpc/pull/19587. + */ + 'sts_service'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService | null); + 'credential_specifier'?: "access_token"|"google_compute_engine"|"google_refresh_token"|"service_account_jwt_access"|"google_iam"|"from_plugin"|"sts_service"; +} + +/** + * [#next-free-field: 8] + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials__Output { + /** + * Access token credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#ad3a80da696ffdaea943f0f858d7a360d. + */ + 'access_token'?: (string); + /** + * Google Compute Engine credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 + */ + 'google_compute_engine'?: (_google_protobuf_Empty__Output | null); + /** + * Google refresh token credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a96901c997b91bc6513b08491e0dca37c. + */ + 'google_refresh_token'?: (string); + /** + * Service Account JWT Access credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a92a9f959d6102461f66ee973d8e9d3aa. + */ + 'service_account_jwt_access'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials__Output | null); + /** + * Google IAM credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a9fc1fc101b41e680d47028166e76f9d0. + */ + 'google_iam'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials__Output | null); + /** + * Custom authenticator credentials. + * https://grpc.io/grpc/cpp/namespacegrpc.html#a823c6a4b19ffc71fb33e90154ee2ad07. + * https://grpc.io/docs/guides/auth.html#extending-grpc-to-support-other-authentication-mechanisms. + */ + 'from_plugin'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output | null); + /** + * Custom security token service which implements OAuth 2.0 token exchange. + * https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16 + * See https://github.com/grpc/grpc/pull/19587. + */ + 'sts_service'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService__Output | null); + 'credential_specifier': "access_token"|"google_compute_engine"|"google_refresh_token"|"service_account_jwt_access"|"google_iam"|"from_plugin"|"sts_service"; +} + +/** + * Channel arguments. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs { + /** + * See grpc_types.h GRPC_ARG #defines for keys that work here. + */ + 'args'?: ({[key: string]: _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value}); +} + +/** + * Channel arguments. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs__Output { + /** + * See grpc_types.h GRPC_ARG #defines for keys that work here. + */ + 'args': ({[key: string]: _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value__Output}); +} + +/** + * See https://grpc.io/docs/guides/auth.html#credential-types to understand Channel and Call + * credential types. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials { + 'ssl_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials | null); + /** + * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 + */ + 'google_default'?: (_google_protobuf_Empty | null); + 'local_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials | null); + 'credential_specifier'?: "ssl_credentials"|"google_default"|"local_credentials"; +} + +/** + * See https://grpc.io/docs/guides/auth.html#credential-types to understand Channel and Call + * credential types. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials__Output { + 'ssl_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials__Output | null); + /** + * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 + */ + 'google_default'?: (_google_protobuf_Empty__Output | null); + 'local_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials__Output | null); + 'credential_specifier': "ssl_credentials"|"google_default"|"local_credentials"; +} + +export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc { + /** + * The name of the upstream gRPC cluster. SSL credentials will be supplied + * in the :ref:`Cluster ` :ref:`transport_socket + * `. + */ + 'cluster_name'?: (string); + /** + * The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. + * Note that this authority does not override the SNI. The SNI is provided by the transport socket of the cluster. + */ + 'authority'?: (string); +} + +export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc__Output { + /** + * The name of the upstream gRPC cluster. SSL credentials will be supplied + * in the :ref:`Cluster ` :ref:`transport_socket + * `. + */ + 'cluster_name': (string); + /** + * The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. + * Note that this authority does not override the SNI. The SNI is provided by the transport socket of the cluster. + */ + 'authority': (string); +} + +/** + * [#next-free-field: 9] + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc { + /** + * The target URI when using the `Google C++ gRPC client + * `_. SSL credentials will be supplied in + * :ref:`channel_credentials `. + */ + 'target_uri'?: (string); + 'channel_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials | null); + /** + * A set of call credentials that can be composed with `channel credentials + * `_. + */ + 'call_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials)[]; + /** + * The human readable prefix to use when emitting statistics for the gRPC + * service. + * + * .. csv-table:: + * :header: Name, Type, Description + * :widths: 1, 1, 2 + * + * streams_total, Counter, Total number of streams opened + * streams_closed_, Counter, Total streams closed with + */ + 'stat_prefix'?: (string); + /** + * The name of the Google gRPC credentials factory to use. This must have been registered with + * Envoy. If this is empty, a default credentials factory will be used that sets up channel + * credentials based on other configuration parameters. + */ + 'credentials_factory_name'?: (string); + /** + * Additional configuration for site-specific customizations of the Google + * gRPC library. + */ + 'config'?: (_google_protobuf_Struct | null); + /** + * How many bytes each stream can buffer internally. + * If not set an implementation defined default is applied (1MiB). + */ + 'per_stream_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); + /** + * Custom channels args. + */ + 'channel_args'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs | null); +} + +/** + * [#next-free-field: 9] + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc__Output { + /** + * The target URI when using the `Google C++ gRPC client + * `_. SSL credentials will be supplied in + * :ref:`channel_credentials `. + */ + 'target_uri': (string); + 'channel_credentials': (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials__Output | null); + /** + * A set of call credentials that can be composed with `channel credentials + * `_. + */ + 'call_credentials': (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials__Output)[]; + /** + * The human readable prefix to use when emitting statistics for the gRPC + * service. + * + * .. csv-table:: + * :header: Name, Type, Description + * :widths: 1, 1, 2 + * + * streams_total, Counter, Total number of streams opened + * streams_closed_, Counter, Total streams closed with + */ + 'stat_prefix': (string); + /** + * The name of the Google gRPC credentials factory to use. This must have been registered with + * Envoy. If this is empty, a default credentials factory will be used that sets up channel + * credentials based on other configuration parameters. + */ + 'credentials_factory_name': (string); + /** + * Additional configuration for site-specific customizations of the Google + * gRPC library. + */ + 'config': (_google_protobuf_Struct__Output | null); + /** + * How many bytes each stream can buffer internally. + * If not set an implementation defined default is applied (1MiB). + */ + 'per_stream_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); + /** + * Custom channels args. + */ + 'channel_args': (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs__Output | null); +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials { + 'authorization_token'?: (string); + 'authority_selector'?: (string); +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials__Output { + 'authorization_token': (string); + 'authority_selector': (string); +} + +/** + * Local channel credentials. Only UDS is supported for now. + * See https://github.com/grpc/grpc/pull/15909. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials { +} + +/** + * Local channel credentials. Only UDS is supported for now. + * See https://github.com/grpc/grpc/pull/15909. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials__Output { +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin { + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * [#extension-category: envoy.grpc_credentials] + */ + 'config_type'?: "typed_config"; +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output { + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * [#extension-category: envoy.grpc_credentials] + */ + 'config_type': "typed_config"; +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials { + 'json_key'?: (string); + 'token_lifetime_seconds'?: (number | string | Long); +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials__Output { + 'json_key': (string); + 'token_lifetime_seconds': (string); +} + +/** + * See https://grpc.io/grpc/cpp/structgrpc_1_1_ssl_credentials_options.html. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials { + /** + * PEM encoded server root certificates. + */ + 'root_certs'?: (_envoy_config_core_v3_DataSource | null); + /** + * PEM encoded client private key. + */ + 'private_key'?: (_envoy_config_core_v3_DataSource | null); + /** + * PEM encoded client certificate chain. + */ + 'cert_chain'?: (_envoy_config_core_v3_DataSource | null); +} + +/** + * See https://grpc.io/grpc/cpp/structgrpc_1_1_ssl_credentials_options.html. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials__Output { + /** + * PEM encoded server root certificates. + */ + 'root_certs': (_envoy_config_core_v3_DataSource__Output | null); + /** + * PEM encoded client private key. + */ + 'private_key': (_envoy_config_core_v3_DataSource__Output | null); + /** + * PEM encoded client certificate chain. + */ + 'cert_chain': (_envoy_config_core_v3_DataSource__Output | null); +} + +/** + * Security token service configuration that allows Google gRPC to + * fetch security token from an OAuth 2.0 authorization server. + * See https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16 and + * https://github.com/grpc/grpc/pull/19587. + * [#next-free-field: 10] + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService { + /** + * URI of the token exchange service that handles token exchange requests. + * [#comment:TODO(asraa): Add URI validation when implemented. Tracked by + * https://github.com/envoyproxy/protoc-gen-validate/issues/303] + */ + 'token_exchange_service_uri'?: (string); + /** + * Location of the target service or resource where the client + * intends to use the requested security token. + */ + 'resource'?: (string); + /** + * Logical name of the target service where the client intends to + * use the requested security token. + */ + 'audience'?: (string); + /** + * The desired scope of the requested security token in the + * context of the service or resource where the token will be used. + */ + 'scope'?: (string); + /** + * Type of the requested security token. + */ + 'requested_token_type'?: (string); + /** + * The path of subject token, a security token that represents the + * identity of the party on behalf of whom the request is being made. + */ + 'subject_token_path'?: (string); + /** + * Type of the subject token. + */ + 'subject_token_type'?: (string); + /** + * The path of actor token, a security token that represents the identity + * of the acting party. The acting party is authorized to use the + * requested security token and act on behalf of the subject. + */ + 'actor_token_path'?: (string); + /** + * Type of the actor token. + */ + 'actor_token_type'?: (string); +} + +/** + * Security token service configuration that allows Google gRPC to + * fetch security token from an OAuth 2.0 authorization server. + * See https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16 and + * https://github.com/grpc/grpc/pull/19587. + * [#next-free-field: 10] + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService__Output { + /** + * URI of the token exchange service that handles token exchange requests. + * [#comment:TODO(asraa): Add URI validation when implemented. Tracked by + * https://github.com/envoyproxy/protoc-gen-validate/issues/303] + */ + 'token_exchange_service_uri': (string); + /** + * Location of the target service or resource where the client + * intends to use the requested security token. + */ + 'resource': (string); + /** + * Logical name of the target service where the client intends to + * use the requested security token. + */ + 'audience': (string); + /** + * The desired scope of the requested security token in the + * context of the service or resource where the token will be used. + */ + 'scope': (string); + /** + * Type of the requested security token. + */ + 'requested_token_type': (string); + /** + * The path of subject token, a security token that represents the + * identity of the party on behalf of whom the request is being made. + */ + 'subject_token_path': (string); + /** + * Type of the subject token. + */ + 'subject_token_type': (string); + /** + * The path of actor token, a security token that represents the identity + * of the acting party. The acting party is authorized to use the + * requested security token and act on behalf of the subject. + */ + 'actor_token_path': (string); + /** + * Type of the actor token. + */ + 'actor_token_type': (string); +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value { + 'string_value'?: (string); + 'int_value'?: (number | string | Long); + /** + * Pointer values are not supported, since they don't make any sense when + * delivered via the API. + */ + 'value_specifier'?: "string_value"|"int_value"; +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value__Output { + 'string_value'?: (string); + 'int_value'?: (string); + /** + * Pointer values are not supported, since they don't make any sense when + * delivered via the API. + */ + 'value_specifier': "string_value"|"int_value"; +} + +/** + * gRPC service configuration. This is used by :ref:`ApiConfigSource + * ` and filter configurations. + * [#next-free-field: 6] + */ +export interface GrpcService { + /** + * Envoy's in-built gRPC client. + * See the :ref:`gRPC services overview ` + * documentation for discussion on gRPC client selection. + */ + 'envoy_grpc'?: (_envoy_config_core_v3_GrpcService_EnvoyGrpc | null); + /** + * `Google C++ gRPC client `_ + * See the :ref:`gRPC services overview ` + * documentation for discussion on gRPC client selection. + */ + 'google_grpc'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc | null); + /** + * The timeout for the gRPC request. This is the timeout for a specific + * request. + */ + 'timeout'?: (_google_protobuf_Duration | null); + /** + * Additional metadata to include in streams initiated to the GrpcService. This can be used for + * scenarios in which additional ad hoc authorization headers (e.g. ``x-foo-bar: baz-key``) are to + * be injected. For more information, including details on header value syntax, see the + * documentation on :ref:`custom request headers + * `. + */ + 'initial_metadata'?: (_envoy_config_core_v3_HeaderValue)[]; + 'target_specifier'?: "envoy_grpc"|"google_grpc"; +} + +/** + * gRPC service configuration. This is used by :ref:`ApiConfigSource + * ` and filter configurations. + * [#next-free-field: 6] + */ +export interface GrpcService__Output { + /** + * Envoy's in-built gRPC client. + * See the :ref:`gRPC services overview ` + * documentation for discussion on gRPC client selection. + */ + 'envoy_grpc'?: (_envoy_config_core_v3_GrpcService_EnvoyGrpc__Output | null); + /** + * `Google C++ gRPC client `_ + * See the :ref:`gRPC services overview ` + * documentation for discussion on gRPC client selection. + */ + 'google_grpc'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc__Output | null); + /** + * The timeout for the gRPC request. This is the timeout for a specific + * request. + */ + 'timeout': (_google_protobuf_Duration__Output | null); + /** + * Additional metadata to include in streams initiated to the GrpcService. This can be used for + * scenarios in which additional ad hoc authorization headers (e.g. ``x-foo-bar: baz-key``) are to + * be injected. For more information, including details on header value syntax, see the + * documentation on :ref:`custom request headers + * `. + */ + 'initial_metadata': (_envoy_config_core_v3_HeaderValue__Output)[]; + 'target_specifier': "envoy_grpc"|"google_grpc"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderMap.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderMap.ts new file mode 100644 index 000000000..3ee496152 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderMap.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { HeaderValue as _envoy_config_core_v3_HeaderValue, HeaderValue__Output as _envoy_config_core_v3_HeaderValue__Output } from '../../../../envoy/config/core/v3/HeaderValue'; + +/** + * Wrapper for a set of headers. + */ +export interface HeaderMap { + 'headers'?: (_envoy_config_core_v3_HeaderValue)[]; +} + +/** + * Wrapper for a set of headers. + */ +export interface HeaderMap__Output { + 'headers': (_envoy_config_core_v3_HeaderValue__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts new file mode 100644 index 000000000..a9fb6c07a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts @@ -0,0 +1,38 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Header name/value pair. + */ +export interface HeaderValue { + /** + * Header name. + */ + 'key'?: (string); + /** + * Header value. + * + * The same :ref:`format specifier ` as used for + * :ref:`HTTP access logging ` applies here, however + * unknown header values are replaced with the empty string instead of `-`. + */ + 'value'?: (string); +} + +/** + * Header name/value pair. + */ +export interface HeaderValue__Output { + /** + * Header name. + */ + 'key': (string); + /** + * Header value. + * + * The same :ref:`format specifier ` as used for + * :ref:`HTTP access logging ` applies here, however + * unknown header values are replaced with the empty string instead of `-`. + */ + 'value': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts new file mode 100644 index 000000000..7ba7e9d8d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts @@ -0,0 +1,69 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { HeaderValue as _envoy_config_core_v3_HeaderValue, HeaderValue__Output as _envoy_config_core_v3_HeaderValue__Output } from '../../../../envoy/config/core/v3/HeaderValue'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; + +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +/** + * Describes the supported actions types for header append action. + */ +export enum _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction { + /** + * This action will append the specified value to the existing values if the header + * already exists. If the header doesn't exist then this will add the header with + * specified key and value. + */ + APPEND_IF_EXISTS_OR_ADD = 0, + /** + * This action will add the header if it doesn't already exist. If the header + * already exists then this will be a no-op. + */ + ADD_IF_ABSENT = 1, + /** + * This action will overwrite the specified value by discarding any existing values if + * the header already exists. If the header doesn't exist then this will add the header + * with specified key and value. + */ + OVERWRITE_IF_EXISTS_OR_ADD = 2, +} + +/** + * Header name/value pair plus option to control append behavior. + */ +export interface HeaderValueOption { + /** + * Header name/value pair that this option applies to. + */ + 'header'?: (_envoy_config_core_v3_HeaderValue | null); + /** + * Should the value be appended? If true (default), the value is appended to + * existing values. Otherwise it replaces any existing values. + */ + 'append'?: (_google_protobuf_BoolValue | null); + /** + * [#not-implemented-hide:] Describes the action taken to append/overwrite the given value for an existing header + * or to only add this header if it's absent. Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD`. + */ + 'append_action'?: (_envoy_config_core_v3_HeaderValueOption_HeaderAppendAction | keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); +} + +/** + * Header name/value pair plus option to control append behavior. + */ +export interface HeaderValueOption__Output { + /** + * Header name/value pair that this option applies to. + */ + 'header': (_envoy_config_core_v3_HeaderValue__Output | null); + /** + * Should the value be appended? If true (default), the value is appended to + * existing values. Otherwise it replaces any existing values. + */ + 'append': (_google_protobuf_BoolValue__Output | null); + /** + * [#not-implemented-hide:] Describes the action taken to append/overwrite the given value for an existing header + * or to only add this header if it's absent. Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD`. + */ + 'append_action': (keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts new file mode 100644 index 000000000..8882de614 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts @@ -0,0 +1,701 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/health_check.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { EventServiceConfig as _envoy_config_core_v3_EventServiceConfig, EventServiceConfig__Output as _envoy_config_core_v3_EventServiceConfig__Output } from '../../../../envoy/config/core/v3/EventServiceConfig'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; +import type { Int64Range as _envoy_type_v3_Int64Range, Int64Range__Output as _envoy_type_v3_Int64Range__Output } from '../../../../envoy/type/v3/Int64Range'; +import type { CodecClientType as _envoy_type_v3_CodecClientType } from '../../../../envoy/type/v3/CodecClientType'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Custom health check. + */ +export interface _envoy_config_core_v3_HealthCheck_CustomHealthCheck { + /** + * The registered name of the custom health checker. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * A custom health checker specific configuration which depends on the custom health checker + * being instantiated. See :api:`envoy/config/health_checker` for reference. + * [#extension-category: envoy.health_checkers] + */ + 'config_type'?: "typed_config"; +} + +/** + * Custom health check. + */ +export interface _envoy_config_core_v3_HealthCheck_CustomHealthCheck__Output { + /** + * The registered name of the custom health checker. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * A custom health checker specific configuration which depends on the custom health checker + * being instantiated. See :api:`envoy/config/health_checker` for reference. + * [#extension-category: envoy.health_checkers] + */ + 'config_type': "typed_config"; +} + +/** + * `grpc.health.v1.Health + * `_-based + * healthcheck. See `gRPC doc `_ + * for details. + */ +export interface _envoy_config_core_v3_HealthCheck_GrpcHealthCheck { + /** + * An optional service name parameter which will be sent to gRPC service in + * `grpc.health.v1.HealthCheckRequest + * `_. + * message. See `gRPC health-checking overview + * `_ for more information. + */ + 'service_name'?: (string); + /** + * The value of the :authority header in the gRPC health check request. If + * left empty (default value), the name of the cluster this health check is associated + * with will be used. The authority header can be customized for a specific endpoint by setting + * the :ref:`hostname ` field. + */ + 'authority'?: (string); +} + +/** + * `grpc.health.v1.Health + * `_-based + * healthcheck. See `gRPC doc `_ + * for details. + */ +export interface _envoy_config_core_v3_HealthCheck_GrpcHealthCheck__Output { + /** + * An optional service name parameter which will be sent to gRPC service in + * `grpc.health.v1.HealthCheckRequest + * `_. + * message. See `gRPC health-checking overview + * `_ for more information. + */ + 'service_name': (string); + /** + * The value of the :authority header in the gRPC health check request. If + * left empty (default value), the name of the cluster this health check is associated + * with will be used. The authority header can be customized for a specific endpoint by setting + * the :ref:`hostname ` field. + */ + 'authority': (string); +} + +/** + * [#next-free-field: 13] + */ +export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { + /** + * The value of the host header in the HTTP health check request. If + * left empty (default value), the name of the cluster this health check is associated + * with will be used. The host header can be customized for a specific endpoint by setting the + * :ref:`hostname ` field. + */ + 'host'?: (string); + /** + * Specifies the HTTP path that will be requested during health checking. For example + * * /healthcheck*. + */ + 'path'?: (string); + /** + * [#not-implemented-hide:] HTTP specific payload. + */ + 'send'?: (_envoy_config_core_v3_HealthCheck_Payload | null); + /** + * [#not-implemented-hide:] HTTP specific response. + */ + 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload | null); + /** + * Specifies a list of HTTP headers that should be added to each request that is sent to the + * health checked cluster. For more information, including details on header value syntax, see + * the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * Specifies a list of HTTP headers that should be removed from each request that is sent to the + * health checked cluster. + */ + 'request_headers_to_remove'?: (string)[]; + /** + * Specifies a list of HTTP response statuses considered healthy. If provided, replaces default + * 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open + * semantics of :ref:`Int64Range `. The start and end of each + * range are required. Only statuses in the range [100, 600) are allowed. + */ + 'expected_statuses'?: (_envoy_type_v3_Int64Range)[]; + /** + * Specifies a list of HTTP response statuses considered retriable. If provided, responses in this range + * will count towards the configured :ref:`unhealthy_threshold `, + * but will not result in the host being considered immediately unhealthy. Ranges follow half-open semantics of + * :ref:`Int64Range `. The start and end of each range are required. + * Only statuses in the range [100, 600) are allowed. The :ref:`expected_statuses ` + * field takes precedence for any range overlaps with this field i.e. if status code 200 is both retriable and expected, a 200 response will + * be considered a successful health check. By default all responses not in + * :ref:`expected_statuses ` will result in + * the host being considered immediately unhealthy i.e. if status code 200 is expected and there are no configured retriable statuses, any + * non-200 response will result in the host being marked unhealthy. + */ + 'retriable_statuses'?: (_envoy_type_v3_Int64Range)[]; + /** + * Use specified application protocol for health checks. + */ + 'codec_client_type'?: (_envoy_type_v3_CodecClientType | keyof typeof _envoy_type_v3_CodecClientType); + /** + * An optional service name parameter which is used to validate the identity of + * the health checked cluster using a :ref:`StringMatcher + * `. See the :ref:`architecture overview + * ` for more information. + */ + 'service_name_matcher'?: (_envoy_type_matcher_v3_StringMatcher | null); +} + +/** + * [#next-free-field: 13] + */ +export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { + /** + * The value of the host header in the HTTP health check request. If + * left empty (default value), the name of the cluster this health check is associated + * with will be used. The host header can be customized for a specific endpoint by setting the + * :ref:`hostname ` field. + */ + 'host': (string); + /** + * Specifies the HTTP path that will be requested during health checking. For example + * * /healthcheck*. + */ + 'path': (string); + /** + * [#not-implemented-hide:] HTTP specific payload. + */ + 'send': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); + /** + * [#not-implemented-hide:] HTTP specific response. + */ + 'receive': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); + /** + * Specifies a list of HTTP headers that should be added to each request that is sent to the + * health checked cluster. For more information, including details on header value syntax, see + * the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * Specifies a list of HTTP headers that should be removed from each request that is sent to the + * health checked cluster. + */ + 'request_headers_to_remove': (string)[]; + /** + * Specifies a list of HTTP response statuses considered healthy. If provided, replaces default + * 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open + * semantics of :ref:`Int64Range `. The start and end of each + * range are required. Only statuses in the range [100, 600) are allowed. + */ + 'expected_statuses': (_envoy_type_v3_Int64Range__Output)[]; + /** + * Specifies a list of HTTP response statuses considered retriable. If provided, responses in this range + * will count towards the configured :ref:`unhealthy_threshold `, + * but will not result in the host being considered immediately unhealthy. Ranges follow half-open semantics of + * :ref:`Int64Range `. The start and end of each range are required. + * Only statuses in the range [100, 600) are allowed. The :ref:`expected_statuses ` + * field takes precedence for any range overlaps with this field i.e. if status code 200 is both retriable and expected, a 200 response will + * be considered a successful health check. By default all responses not in + * :ref:`expected_statuses ` will result in + * the host being considered immediately unhealthy i.e. if status code 200 is expected and there are no configured retriable statuses, any + * non-200 response will result in the host being marked unhealthy. + */ + 'retriable_statuses': (_envoy_type_v3_Int64Range__Output)[]; + /** + * Use specified application protocol for health checks. + */ + 'codec_client_type': (keyof typeof _envoy_type_v3_CodecClientType); + /** + * An optional service name parameter which is used to validate the identity of + * the health checked cluster using a :ref:`StringMatcher + * `. See the :ref:`architecture overview + * ` for more information. + */ + 'service_name_matcher': (_envoy_type_matcher_v3_StringMatcher__Output | null); +} + +/** + * Describes the encoding of the payload bytes in the payload. + */ +export interface _envoy_config_core_v3_HealthCheck_Payload { + /** + * Hex encoded payload. E.g., "000000FF". + */ + 'text'?: (string); + /** + * [#not-implemented-hide:] Binary payload. + */ + 'binary'?: (Buffer | Uint8Array | string); + 'payload'?: "text"|"binary"; +} + +/** + * Describes the encoding of the payload bytes in the payload. + */ +export interface _envoy_config_core_v3_HealthCheck_Payload__Output { + /** + * Hex encoded payload. E.g., "000000FF". + */ + 'text'?: (string); + /** + * [#not-implemented-hide:] Binary payload. + */ + 'binary'?: (Buffer); + 'payload': "text"|"binary"; +} + +export interface _envoy_config_core_v3_HealthCheck_RedisHealthCheck { + /** + * If set, optionally perform ``EXISTS `` instead of ``PING``. A return value + * from Redis of 0 (does not exist) is considered a passing healthcheck. A return value other + * than 0 is considered a failure. This allows the user to mark a Redis instance for maintenance + * by setting the specified key to any value and waiting for traffic to drain. + */ + 'key'?: (string); +} + +export interface _envoy_config_core_v3_HealthCheck_RedisHealthCheck__Output { + /** + * If set, optionally perform ``EXISTS `` instead of ``PING``. A return value + * from Redis of 0 (does not exist) is considered a passing healthcheck. A return value other + * than 0 is considered a failure. This allows the user to mark a Redis instance for maintenance + * by setting the specified key to any value and waiting for traffic to drain. + */ + 'key': (string); +} + +export interface _envoy_config_core_v3_HealthCheck_TcpHealthCheck { + /** + * Empty payloads imply a connect-only health check. + */ + 'send'?: (_envoy_config_core_v3_HealthCheck_Payload | null); + /** + * When checking the response, “fuzzy†matching is performed such that each + * binary block must be found, and in the order specified, but not + * necessarily contiguous. + */ + 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload)[]; +} + +export interface _envoy_config_core_v3_HealthCheck_TcpHealthCheck__Output { + /** + * Empty payloads imply a connect-only health check. + */ + 'send': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); + /** + * When checking the response, “fuzzy†matching is performed such that each + * binary block must be found, and in the order specified, but not + * necessarily contiguous. + */ + 'receive': (_envoy_config_core_v3_HealthCheck_Payload__Output)[]; +} + +/** + * Health checks occur over the transport socket specified for the cluster. This implies that if a + * cluster is using a TLS-enabled transport socket, the health check will also occur over TLS. + * + * This allows overriding the cluster TLS settings, just for health check connections. + */ +export interface _envoy_config_core_v3_HealthCheck_TlsOptions { + /** + * Specifies the ALPN protocols for health check connections. This is useful if the + * corresponding upstream is using ALPN-based :ref:`FilterChainMatch + * ` along with different protocols for health checks + * versus data connections. If empty, no ALPN protocols will be set on health check connections. + */ + 'alpn_protocols'?: (string)[]; +} + +/** + * Health checks occur over the transport socket specified for the cluster. This implies that if a + * cluster is using a TLS-enabled transport socket, the health check will also occur over TLS. + * + * This allows overriding the cluster TLS settings, just for health check connections. + */ +export interface _envoy_config_core_v3_HealthCheck_TlsOptions__Output { + /** + * Specifies the ALPN protocols for health check connections. This is useful if the + * corresponding upstream is using ALPN-based :ref:`FilterChainMatch + * ` along with different protocols for health checks + * versus data connections. If empty, no ALPN protocols will be set on health check connections. + */ + 'alpn_protocols': (string)[]; +} + +/** + * [#next-free-field: 25] + */ +export interface HealthCheck { + /** + * The time to wait for a health check response. If the timeout is reached the + * health check attempt will be considered a failure. + */ + 'timeout'?: (_google_protobuf_Duration | null); + /** + * The interval between health checks. + */ + 'interval'?: (_google_protobuf_Duration | null); + /** + * An optional jitter amount in milliseconds. If specified, during every + * interval Envoy will add interval_jitter to the wait time. + */ + 'interval_jitter'?: (_google_protobuf_Duration | null); + /** + * The number of unhealthy health checks required before a host is marked + * unhealthy. Note that for *http* health checking if a host responds with a code not in + * :ref:`expected_statuses ` + * or :ref:`retriable_statuses `, + * this threshold is ignored and the host is considered immediately unhealthy. + */ + 'unhealthy_threshold'?: (_google_protobuf_UInt32Value | null); + /** + * The number of healthy health checks required before a host is marked + * healthy. Note that during startup, only a single successful health check is + * required to mark a host healthy. + */ + 'healthy_threshold'?: (_google_protobuf_UInt32Value | null); + /** + * [#not-implemented-hide:] Non-serving port for health checking. + */ + 'alt_port'?: (_google_protobuf_UInt32Value | null); + /** + * Reuse health check connection between health checks. Default is true. + */ + 'reuse_connection'?: (_google_protobuf_BoolValue | null); + /** + * HTTP health check. + */ + 'http_health_check'?: (_envoy_config_core_v3_HealthCheck_HttpHealthCheck | null); + /** + * TCP health check. + */ + 'tcp_health_check'?: (_envoy_config_core_v3_HealthCheck_TcpHealthCheck | null); + /** + * gRPC health check. + */ + 'grpc_health_check'?: (_envoy_config_core_v3_HealthCheck_GrpcHealthCheck | null); + /** + * The "no traffic interval" is a special health check interval that is used when a cluster has + * never had traffic routed to it. This lower interval allows cluster information to be kept up to + * date, without sending a potentially large amount of active health checking traffic for no + * reason. Once a cluster has been used for traffic routing, Envoy will shift back to using the + * standard health check interval that is defined. Note that this interval takes precedence over + * any other. + * + * The default value for "no traffic interval" is 60 seconds. + */ + 'no_traffic_interval'?: (_google_protobuf_Duration | null); + /** + * Custom health check. + */ + 'custom_health_check'?: (_envoy_config_core_v3_HealthCheck_CustomHealthCheck | null); + /** + * The "unhealthy interval" is a health check interval that is used for hosts that are marked as + * unhealthy. As soon as the host is marked as healthy, Envoy will shift back to using the + * standard health check interval that is defined. + * + * The default value for "unhealthy interval" is the same as "interval". + */ + 'unhealthy_interval'?: (_google_protobuf_Duration | null); + /** + * The "unhealthy edge interval" is a special health check interval that is used for the first + * health check right after a host is marked as unhealthy. For subsequent health checks + * Envoy will shift back to using either "unhealthy interval" if present or the standard health + * check interval that is defined. + * + * The default value for "unhealthy edge interval" is the same as "unhealthy interval". + */ + 'unhealthy_edge_interval'?: (_google_protobuf_Duration | null); + /** + * The "healthy edge interval" is a special health check interval that is used for the first + * health check right after a host is marked as healthy. For subsequent health checks + * Envoy will shift back to using the standard health check interval that is defined. + * + * The default value for "healthy edge interval" is the same as the default interval. + */ + 'healthy_edge_interval'?: (_google_protobuf_Duration | null); + /** + * Specifies the path to the :ref:`health check event log `. + * If empty, no event log will be written. + */ + 'event_log_path'?: (string); + /** + * An optional jitter amount as a percentage of interval_ms. If specified, + * during every interval Envoy will add interval_ms * + * interval_jitter_percent / 100 to the wait time. + * + * If interval_jitter_ms and interval_jitter_percent are both set, both of + * them will be used to increase the wait time. + */ + 'interval_jitter_percent'?: (number); + /** + * If set to true, health check failure events will always be logged. If set to false, only the + * initial health check failure event will be logged. + * The default value is false. + */ + 'always_log_health_check_failures'?: (boolean); + /** + * An optional jitter amount in milliseconds. If specified, Envoy will start health + * checking after for a random time in ms between 0 and initial_jitter. This only + * applies to the first health check. + */ + 'initial_jitter'?: (_google_protobuf_Duration | null); + /** + * This allows overriding the cluster TLS settings, just for health check connections. + */ + 'tls_options'?: (_envoy_config_core_v3_HealthCheck_TlsOptions | null); + /** + * [#not-implemented-hide:] + * The gRPC service for the health check event service. + * If empty, health check events won't be sent to a remote endpoint. + */ + 'event_service'?: (_envoy_config_core_v3_EventServiceConfig | null); + /** + * Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's + * :ref:`tranport socket matches `. + * For example, the following match criteria + * + * .. code-block:: yaml + * + * transport_socket_match_criteria: + * useMTLS: true + * + * Will match the following :ref:`cluster socket match ` + * + * .. code-block:: yaml + * + * transport_socket_matches: + * - name: "useMTLS" + * match: + * useMTLS: true + * transport_socket: + * name: envoy.transport_sockets.tls + * config: { ... } # tls socket configuration + * + * If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the + * :ref:`LbEndpoint.Metadata `. + * This allows using different transport socket capabilities for health checking versus proxying to the + * endpoint. + * + * If the key/values pairs specified do not match any + * :ref:`transport socket matches `, + * the cluster's :ref:`transport socket ` + * will be used for health check socket configuration. + */ + 'transport_socket_match_criteria'?: (_google_protobuf_Struct | null); + /** + * The "no traffic healthy interval" is a special health check interval that + * is used for hosts that are currently passing active health checking + * (including new hosts) when the cluster has received no traffic. + * + * This is useful for when we want to send frequent health checks with + * `no_traffic_interval` but then revert to lower frequency `no_traffic_healthy_interval` once + * a host in the cluster is marked as healthy. + * + * Once a cluster has been used for traffic routing, Envoy will shift back to using the + * standard health check interval that is defined. + * + * If no_traffic_healthy_interval is not set, it will default to the + * no traffic interval and send that interval regardless of health state. + */ + 'no_traffic_healthy_interval'?: (_google_protobuf_Duration | null); + 'health_checker'?: "http_health_check"|"tcp_health_check"|"grpc_health_check"|"custom_health_check"; +} + +/** + * [#next-free-field: 25] + */ +export interface HealthCheck__Output { + /** + * The time to wait for a health check response. If the timeout is reached the + * health check attempt will be considered a failure. + */ + 'timeout': (_google_protobuf_Duration__Output | null); + /** + * The interval between health checks. + */ + 'interval': (_google_protobuf_Duration__Output | null); + /** + * An optional jitter amount in milliseconds. If specified, during every + * interval Envoy will add interval_jitter to the wait time. + */ + 'interval_jitter': (_google_protobuf_Duration__Output | null); + /** + * The number of unhealthy health checks required before a host is marked + * unhealthy. Note that for *http* health checking if a host responds with a code not in + * :ref:`expected_statuses ` + * or :ref:`retriable_statuses `, + * this threshold is ignored and the host is considered immediately unhealthy. + */ + 'unhealthy_threshold': (_google_protobuf_UInt32Value__Output | null); + /** + * The number of healthy health checks required before a host is marked + * healthy. Note that during startup, only a single successful health check is + * required to mark a host healthy. + */ + 'healthy_threshold': (_google_protobuf_UInt32Value__Output | null); + /** + * [#not-implemented-hide:] Non-serving port for health checking. + */ + 'alt_port': (_google_protobuf_UInt32Value__Output | null); + /** + * Reuse health check connection between health checks. Default is true. + */ + 'reuse_connection': (_google_protobuf_BoolValue__Output | null); + /** + * HTTP health check. + */ + 'http_health_check'?: (_envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output | null); + /** + * TCP health check. + */ + 'tcp_health_check'?: (_envoy_config_core_v3_HealthCheck_TcpHealthCheck__Output | null); + /** + * gRPC health check. + */ + 'grpc_health_check'?: (_envoy_config_core_v3_HealthCheck_GrpcHealthCheck__Output | null); + /** + * The "no traffic interval" is a special health check interval that is used when a cluster has + * never had traffic routed to it. This lower interval allows cluster information to be kept up to + * date, without sending a potentially large amount of active health checking traffic for no + * reason. Once a cluster has been used for traffic routing, Envoy will shift back to using the + * standard health check interval that is defined. Note that this interval takes precedence over + * any other. + * + * The default value for "no traffic interval" is 60 seconds. + */ + 'no_traffic_interval': (_google_protobuf_Duration__Output | null); + /** + * Custom health check. + */ + 'custom_health_check'?: (_envoy_config_core_v3_HealthCheck_CustomHealthCheck__Output | null); + /** + * The "unhealthy interval" is a health check interval that is used for hosts that are marked as + * unhealthy. As soon as the host is marked as healthy, Envoy will shift back to using the + * standard health check interval that is defined. + * + * The default value for "unhealthy interval" is the same as "interval". + */ + 'unhealthy_interval': (_google_protobuf_Duration__Output | null); + /** + * The "unhealthy edge interval" is a special health check interval that is used for the first + * health check right after a host is marked as unhealthy. For subsequent health checks + * Envoy will shift back to using either "unhealthy interval" if present or the standard health + * check interval that is defined. + * + * The default value for "unhealthy edge interval" is the same as "unhealthy interval". + */ + 'unhealthy_edge_interval': (_google_protobuf_Duration__Output | null); + /** + * The "healthy edge interval" is a special health check interval that is used for the first + * health check right after a host is marked as healthy. For subsequent health checks + * Envoy will shift back to using the standard health check interval that is defined. + * + * The default value for "healthy edge interval" is the same as the default interval. + */ + 'healthy_edge_interval': (_google_protobuf_Duration__Output | null); + /** + * Specifies the path to the :ref:`health check event log `. + * If empty, no event log will be written. + */ + 'event_log_path': (string); + /** + * An optional jitter amount as a percentage of interval_ms. If specified, + * during every interval Envoy will add interval_ms * + * interval_jitter_percent / 100 to the wait time. + * + * If interval_jitter_ms and interval_jitter_percent are both set, both of + * them will be used to increase the wait time. + */ + 'interval_jitter_percent': (number); + /** + * If set to true, health check failure events will always be logged. If set to false, only the + * initial health check failure event will be logged. + * The default value is false. + */ + 'always_log_health_check_failures': (boolean); + /** + * An optional jitter amount in milliseconds. If specified, Envoy will start health + * checking after for a random time in ms between 0 and initial_jitter. This only + * applies to the first health check. + */ + 'initial_jitter': (_google_protobuf_Duration__Output | null); + /** + * This allows overriding the cluster TLS settings, just for health check connections. + */ + 'tls_options': (_envoy_config_core_v3_HealthCheck_TlsOptions__Output | null); + /** + * [#not-implemented-hide:] + * The gRPC service for the health check event service. + * If empty, health check events won't be sent to a remote endpoint. + */ + 'event_service': (_envoy_config_core_v3_EventServiceConfig__Output | null); + /** + * Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's + * :ref:`tranport socket matches `. + * For example, the following match criteria + * + * .. code-block:: yaml + * + * transport_socket_match_criteria: + * useMTLS: true + * + * Will match the following :ref:`cluster socket match ` + * + * .. code-block:: yaml + * + * transport_socket_matches: + * - name: "useMTLS" + * match: + * useMTLS: true + * transport_socket: + * name: envoy.transport_sockets.tls + * config: { ... } # tls socket configuration + * + * If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the + * :ref:`LbEndpoint.Metadata `. + * This allows using different transport socket capabilities for health checking versus proxying to the + * endpoint. + * + * If the key/values pairs specified do not match any + * :ref:`transport socket matches `, + * the cluster's :ref:`transport socket ` + * will be used for health check socket configuration. + */ + 'transport_socket_match_criteria': (_google_protobuf_Struct__Output | null); + /** + * The "no traffic healthy interval" is a special health check interval that + * is used for hosts that are currently passing active health checking + * (including new hosts) when the cluster has received no traffic. + * + * This is useful for when we want to send frequent health checks with + * `no_traffic_interval` but then revert to lower frequency `no_traffic_healthy_interval` once + * a host in the cluster is marked as healthy. + * + * Once a cluster has been used for traffic routing, Envoy will shift back to using the + * standard health check interval that is defined. + * + * If no_traffic_healthy_interval is not set, it will default to the + * no traffic interval and send that interval regardless of health state. + */ + 'no_traffic_healthy_interval': (_google_protobuf_Duration__Output | null); + 'health_checker': "http_health_check"|"tcp_health_check"|"grpc_health_check"|"custom_health_check"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts new file mode 100644 index 000000000..6ecbca272 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/health_check.proto + +/** + * Endpoint health status. + */ +export enum HealthStatus { + /** + * The health status is not known. This is interpreted by Envoy as *HEALTHY*. + */ + UNKNOWN = 0, + /** + * Healthy. + */ + HEALTHY = 1, + /** + * Unhealthy. + */ + UNHEALTHY = 2, + /** + * Connection draining in progress. E.g., + * ``_ + * or + * ``_. + * This is interpreted by Envoy as *UNHEALTHY*. + */ + DRAINING = 3, + /** + * Health check timed out. This is part of HDS and is interpreted by Envoy as + * *UNHEALTHY*. + */ + TIMEOUT = 4, + /** + * Degraded. + */ + DEGRADED = 5, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts new file mode 100644 index 000000000..40b7408f6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts @@ -0,0 +1,178 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * [#next-free-field: 9] + */ +export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat { + /** + * Formats the header by proper casing words: the first character and any character following + * a special character will be capitalized if it's an alpha character. For example, + * "content-type" becomes "Content-Type", and "foo$b#$are" becomes "Foo$B#$Are". + * Note that while this results in most headers following conventional casing, certain headers + * are not covered. For example, the "TE" header will be formatted as "Te". + */ + 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords | null); + /** + * Configuration for stateful formatter extensions that allow using received headers to + * affect the output of encoding headers. E.g., preserving case during proxying. + * [#extension-category: envoy.http.stateful_header_formatters] + */ + 'stateful_formatter'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + 'header_format'?: "proper_case_words"|"stateful_formatter"; +} + +/** + * [#next-free-field: 9] + */ +export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Output { + /** + * Formats the header by proper casing words: the first character and any character following + * a special character will be capitalized if it's an alpha character. For example, + * "content-type" becomes "Content-Type", and "foo$b#$are" becomes "Foo$B#$Are". + * Note that while this results in most headers following conventional casing, certain headers + * are not covered. For example, the "TE" header will be formatted as "Te". + */ + 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output | null); + /** + * Configuration for stateful formatter extensions that allow using received headers to + * affect the output of encoding headers. E.g., preserving case during proxying. + * [#extension-category: envoy.http.stateful_header_formatters] + */ + 'stateful_formatter'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + 'header_format': "proper_case_words"|"stateful_formatter"; +} + +export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords { +} + +export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output { +} + +/** + * [#next-free-field: 8] + */ +export interface Http1ProtocolOptions { + /** + * Handle HTTP requests with absolute URLs in the requests. These requests + * are generally sent by clients to forward/explicit proxies. This allows clients to configure + * envoy as their HTTP proxy. In Unix, for example, this is typically done by setting the + * *http_proxy* environment variable. + */ + 'allow_absolute_url'?: (_google_protobuf_BoolValue | null); + /** + * Handle incoming HTTP/1.0 and HTTP 0.9 requests. + * This is off by default, and not fully standards compliant. There is support for pre-HTTP/1.1 + * style connect logic, dechunking, and handling lack of client host iff + * *default_host_for_http_10* is configured. + */ + 'accept_http_10'?: (boolean); + /** + * A default host for HTTP/1.0 requests. This is highly suggested if *accept_http_10* is true as + * Envoy does not otherwise support HTTP/1.0 without a Host header. + * This is a no-op if *accept_http_10* is not true. + */ + 'default_host_for_http_10'?: (string); + /** + * Describes how the keys for response headers should be formatted. By default, all header keys + * are lower cased. + */ + 'header_key_format'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat | null); + /** + * Enables trailers for HTTP/1. By default the HTTP/1 codec drops proxied trailers. + * + * .. attention:: + * + * Note that this only happens when Envoy is chunk encoding which occurs when: + * - The request is HTTP/1.1. + * - Is neither a HEAD only request nor a HTTP Upgrade. + * - Not a response to a HEAD request. + * - The content length header is not present. + */ + 'enable_trailers'?: (boolean); + /** + * Allows Envoy to process requests/responses with both `Content-Length` and `Transfer-Encoding` + * headers set. By default such messages are rejected, but if option is enabled - Envoy will + * remove Content-Length header and process message. + * See `RFC7230, sec. 3.3.3 ` for details. + * + * .. attention:: + * Enabling this option might lead to request smuggling vulnerability, especially if traffic + * is proxied via multiple layers of proxies. + */ + 'allow_chunked_length'?: (boolean); + /** + * Allows invalid HTTP messaging. When this option is false, then Envoy will terminate + * HTTP/1.1 connections upon receiving an invalid HTTP message. However, + * when this option is true, then Envoy will leave the HTTP/1.1 connection + * open where possible. + * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * `. + */ + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); +} + +/** + * [#next-free-field: 8] + */ +export interface Http1ProtocolOptions__Output { + /** + * Handle HTTP requests with absolute URLs in the requests. These requests + * are generally sent by clients to forward/explicit proxies. This allows clients to configure + * envoy as their HTTP proxy. In Unix, for example, this is typically done by setting the + * *http_proxy* environment variable. + */ + 'allow_absolute_url': (_google_protobuf_BoolValue__Output | null); + /** + * Handle incoming HTTP/1.0 and HTTP 0.9 requests. + * This is off by default, and not fully standards compliant. There is support for pre-HTTP/1.1 + * style connect logic, dechunking, and handling lack of client host iff + * *default_host_for_http_10* is configured. + */ + 'accept_http_10': (boolean); + /** + * A default host for HTTP/1.0 requests. This is highly suggested if *accept_http_10* is true as + * Envoy does not otherwise support HTTP/1.0 without a Host header. + * This is a no-op if *accept_http_10* is not true. + */ + 'default_host_for_http_10': (string); + /** + * Describes how the keys for response headers should be formatted. By default, all header keys + * are lower cased. + */ + 'header_key_format': (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Output | null); + /** + * Enables trailers for HTTP/1. By default the HTTP/1 codec drops proxied trailers. + * + * .. attention:: + * + * Note that this only happens when Envoy is chunk encoding which occurs when: + * - The request is HTTP/1.1. + * - Is neither a HEAD only request nor a HTTP Upgrade. + * - Not a response to a HEAD request. + * - The content length header is not present. + */ + 'enable_trailers': (boolean); + /** + * Allows Envoy to process requests/responses with both `Content-Length` and `Transfer-Encoding` + * headers set. By default such messages are rejected, but if option is enabled - Envoy will + * remove Content-Length header and process message. + * See `RFC7230, sec. 3.3.3 ` for details. + * + * .. attention:: + * Enabling this option might lead to request smuggling vulnerability, especially if traffic + * is proxied via multiple layers of proxies. + */ + 'allow_chunked_length': (boolean); + /** + * Allows invalid HTTP messaging. When this option is false, then Envoy will terminate + * HTTP/1.1 connections upon receiving an invalid HTTP message. However, + * when this option is true, then Envoy will leave the HTTP/1.1 connection + * open where possible. + * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * `. + */ + 'override_stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts new file mode 100644 index 000000000..786e2a00d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts @@ -0,0 +1,403 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { KeepaliveSettings as _envoy_config_core_v3_KeepaliveSettings, KeepaliveSettings__Output as _envoy_config_core_v3_KeepaliveSettings__Output } from '../../../../envoy/config/core/v3/KeepaliveSettings'; + +/** + * Defines a parameter to be sent in the SETTINGS frame. + * See `RFC7540, sec. 6.5.1 `_ for details. + */ +export interface _envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter { + /** + * The 16 bit parameter identifier. + */ + 'identifier'?: (_google_protobuf_UInt32Value | null); + /** + * The 32 bit parameter value. + */ + 'value'?: (_google_protobuf_UInt32Value | null); +} + +/** + * Defines a parameter to be sent in the SETTINGS frame. + * See `RFC7540, sec. 6.5.1 `_ for details. + */ +export interface _envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter__Output { + /** + * The 16 bit parameter identifier. + */ + 'identifier': (_google_protobuf_UInt32Value__Output | null); + /** + * The 32 bit parameter value. + */ + 'value': (_google_protobuf_UInt32Value__Output | null); +} + +/** + * [#next-free-field: 16] + */ +export interface Http2ProtocolOptions { + /** + * `Maximum table size `_ + * (in octets) that the encoder is permitted to use for the dynamic HPACK table. Valid values + * range from 0 to 4294967295 (2^32 - 1) and defaults to 4096. 0 effectively disables header + * compression. + */ + 'hpack_table_size'?: (_google_protobuf_UInt32Value | null); + /** + * `Maximum concurrent streams `_ + * allowed for peer on one HTTP/2 connection. Valid values range from 1 to 2147483647 (2^31 - 1) + * and defaults to 2147483647. + * + * For upstream connections, this also limits how many streams Envoy will initiate concurrently + * on a single connection. If the limit is reached, Envoy may queue requests or establish + * additional connections (as allowed per circuit breaker limits). + * + * This acts as an upper bound: Envoy will lower the max concurrent streams allowed on a given + * connection based on upstream settings. Config dumps will reflect the configured upper bound, + * not the per-connection negotiated limits. + */ + 'max_concurrent_streams'?: (_google_protobuf_UInt32Value | null); + /** + * `Initial stream-level flow-control window + * `_ size. Valid values range from 65535 + * (2^16 - 1, HTTP/2 default) to 2147483647 (2^31 - 1, HTTP/2 maximum) and defaults to 268435456 + * (256 * 1024 * 1024). + * + * NOTE: 65535 is the initial window size from HTTP/2 spec. We only support increasing the default + * window size now, so it's also the minimum. + * + * This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + * HTTP/2 codec buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + * stop the flow of data to the codec buffers. + */ + 'initial_stream_window_size'?: (_google_protobuf_UInt32Value | null); + /** + * Similar to *initial_stream_window_size*, but for connection-level flow-control + * window. Currently, this has the same minimum/maximum/default as *initial_stream_window_size*. + */ + 'initial_connection_window_size'?: (_google_protobuf_UInt32Value | null); + /** + * Allows proxying Websocket and other upgrades over H2 connect. + */ + 'allow_connect'?: (boolean); + /** + * [#not-implemented-hide:] Hiding until envoy has full metadata support. + * Still under implementation. DO NOT USE. + * + * Allows metadata. See [metadata + * docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more + * information. + */ + 'allow_metadata'?: (boolean); + /** + * Limit the number of pending outbound downstream frames of all types (frames that are waiting to + * be written into the socket). Exceeding this limit triggers flood mitigation and connection is + * terminated. The ``http2.outbound_flood`` stat tracks the number of terminated connections due + * to flood mitigation. The default limit is 10000. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_outbound_frames'?: (_google_protobuf_UInt32Value | null); + /** + * Limit the number of pending outbound downstream frames of types PING, SETTINGS and RST_STREAM, + * preventing high memory utilization when receiving continuous stream of these frames. Exceeding + * this limit triggers flood mitigation and connection is terminated. The + * ``http2.outbound_control_flood`` stat tracks the number of terminated connections due to flood + * mitigation. The default limit is 1000. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_outbound_control_frames'?: (_google_protobuf_UInt32Value | null); + /** + * Limit the number of consecutive inbound frames of types HEADERS, CONTINUATION and DATA with an + * empty payload and no end stream flag. Those frames have no legitimate use and are abusive, but + * might be a result of a broken HTTP/2 implementation. The `http2.inbound_empty_frames_flood`` + * stat tracks the number of connections terminated due to flood mitigation. + * Setting this to 0 will terminate connection upon receiving first frame with an empty payload + * and no end stream flag. The default limit is 1. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_consecutive_inbound_frames_with_empty_payload'?: (_google_protobuf_UInt32Value | null); + /** + * Limit the number of inbound PRIORITY frames allowed per each opened stream. If the number + * of PRIORITY frames received over the lifetime of connection exceeds the value calculated + * using this formula:: + * + * max_inbound_priority_frames_per_stream * (1 + opened_streams) + * + * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * Envoy receives complete response headers from the upstream server. For upstream connection the + * `opened_streams` is incremented when Envoy send the HEADERS frame for a new stream. The + * ``http2.inbound_priority_frames_flood`` stat tracks + * the number of connections terminated due to flood mitigation. The default limit is 100. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_inbound_priority_frames_per_stream'?: (_google_protobuf_UInt32Value | null); + /** + * Limit the number of inbound WINDOW_UPDATE frames allowed per DATA frame sent. If the number + * of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated + * using this formula:: + * + * 5 + 2 * (opened_streams + + * max_inbound_window_update_frames_per_data_frame_sent * outbound_data_frames) + * + * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * Envoy receives complete response headers from the upstream server. For upstream connections the + * `opened_streams` is incremented when Envoy sends the HEADERS frame for a new stream. The + * ``http2.inbound_priority_frames_flood`` stat tracks the number of connections terminated due to + * flood mitigation. The default max_inbound_window_update_frames_per_data_frame_sent value is 10. + * Setting this to 1 should be enough to support HTTP/2 implementations with basic flow control, + * but more complex implementations that try to estimate available bandwidth require at least 2. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_inbound_window_update_frames_per_data_frame_sent'?: (_google_protobuf_UInt32Value | null); + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * This is overridden by HCM :ref:`stream_error_on_invalid_http_messaging + * ` + * iff present. + * + * This is deprecated in favor of :ref:`override_stream_error_on_invalid_http_message + * ` + * + * See `RFC7540, sec. 8.1 `_ for details. + */ + 'stream_error_on_invalid_http_messaging'?: (boolean); + /** + * [#not-implemented-hide:] + * Specifies SETTINGS frame parameters to be sent to the peer, with two exceptions: + * + * 1. SETTINGS_ENABLE_PUSH (0x2) is not configurable as HTTP/2 server push is not supported by + * Envoy. + * + * 2. SETTINGS_ENABLE_CONNECT_PROTOCOL (0x8) is only configurable through the named field + * 'allow_connect'. + * + * Note that custom parameters specified through this field can not also be set in the + * corresponding named parameters: + * + * .. code-block:: text + * + * ID Field Name + * ---------------- + * 0x1 hpack_table_size + * 0x3 max_concurrent_streams + * 0x4 initial_stream_window_size + * + * Collisions will trigger config validation failure on load/update. Likewise, inconsistencies + * between custom parameters with the same identifier will trigger a failure. + * + * See `IANA HTTP/2 Settings + * `_ for + * standardized identifiers. + */ + 'custom_settings_parameters'?: (_envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter)[]; + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * This overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * ` + * + * See `RFC7540, sec. 8.1 `_ for details. + */ + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); + /** + * Send HTTP/2 PING frames to verify that the connection is still healthy. If the remote peer + * does not respond within the configured timeout, the connection will be aborted. + */ + 'connection_keepalive'?: (_envoy_config_core_v3_KeepaliveSettings | null); +} + +/** + * [#next-free-field: 16] + */ +export interface Http2ProtocolOptions__Output { + /** + * `Maximum table size `_ + * (in octets) that the encoder is permitted to use for the dynamic HPACK table. Valid values + * range from 0 to 4294967295 (2^32 - 1) and defaults to 4096. 0 effectively disables header + * compression. + */ + 'hpack_table_size': (_google_protobuf_UInt32Value__Output | null); + /** + * `Maximum concurrent streams `_ + * allowed for peer on one HTTP/2 connection. Valid values range from 1 to 2147483647 (2^31 - 1) + * and defaults to 2147483647. + * + * For upstream connections, this also limits how many streams Envoy will initiate concurrently + * on a single connection. If the limit is reached, Envoy may queue requests or establish + * additional connections (as allowed per circuit breaker limits). + * + * This acts as an upper bound: Envoy will lower the max concurrent streams allowed on a given + * connection based on upstream settings. Config dumps will reflect the configured upper bound, + * not the per-connection negotiated limits. + */ + 'max_concurrent_streams': (_google_protobuf_UInt32Value__Output | null); + /** + * `Initial stream-level flow-control window + * `_ size. Valid values range from 65535 + * (2^16 - 1, HTTP/2 default) to 2147483647 (2^31 - 1, HTTP/2 maximum) and defaults to 268435456 + * (256 * 1024 * 1024). + * + * NOTE: 65535 is the initial window size from HTTP/2 spec. We only support increasing the default + * window size now, so it's also the minimum. + * + * This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + * HTTP/2 codec buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + * stop the flow of data to the codec buffers. + */ + 'initial_stream_window_size': (_google_protobuf_UInt32Value__Output | null); + /** + * Similar to *initial_stream_window_size*, but for connection-level flow-control + * window. Currently, this has the same minimum/maximum/default as *initial_stream_window_size*. + */ + 'initial_connection_window_size': (_google_protobuf_UInt32Value__Output | null); + /** + * Allows proxying Websocket and other upgrades over H2 connect. + */ + 'allow_connect': (boolean); + /** + * [#not-implemented-hide:] Hiding until envoy has full metadata support. + * Still under implementation. DO NOT USE. + * + * Allows metadata. See [metadata + * docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more + * information. + */ + 'allow_metadata': (boolean); + /** + * Limit the number of pending outbound downstream frames of all types (frames that are waiting to + * be written into the socket). Exceeding this limit triggers flood mitigation and connection is + * terminated. The ``http2.outbound_flood`` stat tracks the number of terminated connections due + * to flood mitigation. The default limit is 10000. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_outbound_frames': (_google_protobuf_UInt32Value__Output | null); + /** + * Limit the number of pending outbound downstream frames of types PING, SETTINGS and RST_STREAM, + * preventing high memory utilization when receiving continuous stream of these frames. Exceeding + * this limit triggers flood mitigation and connection is terminated. The + * ``http2.outbound_control_flood`` stat tracks the number of terminated connections due to flood + * mitigation. The default limit is 1000. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_outbound_control_frames': (_google_protobuf_UInt32Value__Output | null); + /** + * Limit the number of consecutive inbound frames of types HEADERS, CONTINUATION and DATA with an + * empty payload and no end stream flag. Those frames have no legitimate use and are abusive, but + * might be a result of a broken HTTP/2 implementation. The `http2.inbound_empty_frames_flood`` + * stat tracks the number of connections terminated due to flood mitigation. + * Setting this to 0 will terminate connection upon receiving first frame with an empty payload + * and no end stream flag. The default limit is 1. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_consecutive_inbound_frames_with_empty_payload': (_google_protobuf_UInt32Value__Output | null); + /** + * Limit the number of inbound PRIORITY frames allowed per each opened stream. If the number + * of PRIORITY frames received over the lifetime of connection exceeds the value calculated + * using this formula:: + * + * max_inbound_priority_frames_per_stream * (1 + opened_streams) + * + * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * Envoy receives complete response headers from the upstream server. For upstream connection the + * `opened_streams` is incremented when Envoy send the HEADERS frame for a new stream. The + * ``http2.inbound_priority_frames_flood`` stat tracks + * the number of connections terminated due to flood mitigation. The default limit is 100. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_inbound_priority_frames_per_stream': (_google_protobuf_UInt32Value__Output | null); + /** + * Limit the number of inbound WINDOW_UPDATE frames allowed per DATA frame sent. If the number + * of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated + * using this formula:: + * + * 5 + 2 * (opened_streams + + * max_inbound_window_update_frames_per_data_frame_sent * outbound_data_frames) + * + * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * Envoy receives complete response headers from the upstream server. For upstream connections the + * `opened_streams` is incremented when Envoy sends the HEADERS frame for a new stream. The + * ``http2.inbound_priority_frames_flood`` stat tracks the number of connections terminated due to + * flood mitigation. The default max_inbound_window_update_frames_per_data_frame_sent value is 10. + * Setting this to 1 should be enough to support HTTP/2 implementations with basic flow control, + * but more complex implementations that try to estimate available bandwidth require at least 2. + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. + */ + 'max_inbound_window_update_frames_per_data_frame_sent': (_google_protobuf_UInt32Value__Output | null); + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * This is overridden by HCM :ref:`stream_error_on_invalid_http_messaging + * ` + * iff present. + * + * This is deprecated in favor of :ref:`override_stream_error_on_invalid_http_message + * ` + * + * See `RFC7540, sec. 8.1 `_ for details. + */ + 'stream_error_on_invalid_http_messaging': (boolean); + /** + * [#not-implemented-hide:] + * Specifies SETTINGS frame parameters to be sent to the peer, with two exceptions: + * + * 1. SETTINGS_ENABLE_PUSH (0x2) is not configurable as HTTP/2 server push is not supported by + * Envoy. + * + * 2. SETTINGS_ENABLE_CONNECT_PROTOCOL (0x8) is only configurable through the named field + * 'allow_connect'. + * + * Note that custom parameters specified through this field can not also be set in the + * corresponding named parameters: + * + * .. code-block:: text + * + * ID Field Name + * ---------------- + * 0x1 hpack_table_size + * 0x3 max_concurrent_streams + * 0x4 initial_stream_window_size + * + * Collisions will trigger config validation failure on load/update. Likewise, inconsistencies + * between custom parameters with the same identifier will trigger a failure. + * + * See `IANA HTTP/2 Settings + * `_ for + * standardized identifiers. + */ + 'custom_settings_parameters': (_envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter__Output)[]; + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * This overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * ` + * + * See `RFC7540, sec. 8.1 `_ for details. + */ + 'override_stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); + /** + * Send HTTP/2 PING frames to verify that the connection is still healthy. If the remote peer + * does not respond within the configured timeout, the connection will be aborted. + */ + 'connection_keepalive': (_envoy_config_core_v3_KeepaliveSettings__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts new file mode 100644 index 000000000..51b31b8e7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts @@ -0,0 +1,56 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { QuicProtocolOptions as _envoy_config_core_v3_QuicProtocolOptions, QuicProtocolOptions__Output as _envoy_config_core_v3_QuicProtocolOptions__Output } from '../../../../envoy/config/core/v3/QuicProtocolOptions'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; + +/** + * A message which allows using HTTP/3. + * [#next-free-field: 6] + */ +export interface Http3ProtocolOptions { + 'quic_protocol_options'?: (_envoy_config_core_v3_QuicProtocolOptions | null); + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/3 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * `. + */ + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); + /** + * Allows proxying Websocket and other upgrades over HTTP/3 CONNECT using + * the header mechanisms from the `HTTP/2 extended connect RFC + * `_ + * and settings `proposed for HTTP/3 + * `_ + * Note that HTTP/3 CONNECT is not yet an RFC. + */ + 'allow_extended_connect'?: (boolean); +} + +/** + * A message which allows using HTTP/3. + * [#next-free-field: 6] + */ +export interface Http3ProtocolOptions__Output { + 'quic_protocol_options': (_envoy_config_core_v3_QuicProtocolOptions__Output | null); + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/3 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * `. + */ + 'override_stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); + /** + * Allows proxying Websocket and other upgrades over HTTP/3 CONNECT using + * the header mechanisms from the `HTTP/2 extended connect RFC + * `_ + * and settings `proposed for HTTP/3 + * `_ + * Note that HTTP/3 CONNECT is not yet an RFC. + */ + 'allow_extended_connect': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts new file mode 100644 index 000000000..34a4053dd --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts @@ -0,0 +1,150 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +/** + * Action to take when Envoy receives client request with header names containing underscore + * characters. + * Underscore character is allowed in header names by the RFC-7230 and this behavior is implemented + * as a security measure due to systems that treat '_' and '-' as interchangeable. Envoy by default allows client request headers with underscore + * characters. + */ +export enum _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction { + /** + * Allow headers with underscores. This is the default behavior. + */ + ALLOW = 0, + /** + * Reject client request. HTTP/1 requests are rejected with the 400 status. HTTP/2 requests + * end with the stream reset. The "httpN.requests_rejected_with_underscores_in_headers" counter + * is incremented for each rejected request. + */ + REJECT_REQUEST = 1, + /** + * Drop the header with name containing underscores. The header is dropped before the filter chain is + * invoked and as such filters will not see dropped headers. The + * "httpN.dropped_headers_with_underscores" is incremented for each dropped header. + */ + DROP_HEADER = 2, +} + +/** + * [#next-free-field: 7] + */ +export interface HttpProtocolOptions { + /** + * The idle timeout for connections. The idle timeout is defined as the + * period in which there are no active requests. When the + * idle timeout is reached the connection will be closed. If the connection is an HTTP/2 + * downstream connection a drain sequence will occur prior to closing the connection, see + * :ref:`drain_timeout + * `. + * Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. + * If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. + * + * .. warning:: + * Disabling this timeout has a highly likelihood of yielding connection leaks due to lost TCP + * FIN packets, etc. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled for downstream connections according to the value for + * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. + */ + 'idle_timeout'?: (_google_protobuf_Duration | null); + /** + * The maximum number of headers. If unconfigured, the default + * maximum number of request headers allowed is 100. Requests that exceed this limit will receive + * a 431 response for HTTP/1.x and cause a stream reset for HTTP/2. + */ + 'max_headers_count'?: (_google_protobuf_UInt32Value | null); + /** + * The maximum duration of a connection. The duration is defined as a period since a connection + * was established. If not set, there is no max duration. When max_connection_duration is reached + * and if there are no active streams, the connection will be closed. If there are any active streams, + * the drain sequence will kick-in, and the connection will be force-closed after the drain period. + * See :ref:`drain_timeout + * `. + * Note: This feature is not yet implemented for the upstream connections. + */ + 'max_connection_duration'?: (_google_protobuf_Duration | null); + /** + * Total duration to keep alive an HTTP request/response stream. If the time limit is reached the stream will be + * reset independent of any other timeouts. If not specified, this value is not set. + */ + 'max_stream_duration'?: (_google_protobuf_Duration | null); + /** + * Action to take when a client request with a header name containing underscore characters is received. + * If this setting is not specified, the value defaults to ALLOW. + * Note: upstream responses are not affected by this setting. + */ + 'headers_with_underscores_action'?: (_envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction | keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); + /** + * Optional maximum requests for both upstream and downstream connections. + * If not specified, there is no limit. + * Setting this parameter to 1 will effectively disable keep alive. + * For HTTP/2 and HTTP/3, due to concurrent stream processing, the limit is approximate. + */ + 'max_requests_per_connection'?: (_google_protobuf_UInt32Value | null); +} + +/** + * [#next-free-field: 7] + */ +export interface HttpProtocolOptions__Output { + /** + * The idle timeout for connections. The idle timeout is defined as the + * period in which there are no active requests. When the + * idle timeout is reached the connection will be closed. If the connection is an HTTP/2 + * downstream connection a drain sequence will occur prior to closing the connection, see + * :ref:`drain_timeout + * `. + * Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. + * If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. + * + * .. warning:: + * Disabling this timeout has a highly likelihood of yielding connection leaks due to lost TCP + * FIN packets, etc. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled for downstream connections according to the value for + * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. + */ + 'idle_timeout': (_google_protobuf_Duration__Output | null); + /** + * The maximum number of headers. If unconfigured, the default + * maximum number of request headers allowed is 100. Requests that exceed this limit will receive + * a 431 response for HTTP/1.x and cause a stream reset for HTTP/2. + */ + 'max_headers_count': (_google_protobuf_UInt32Value__Output | null); + /** + * The maximum duration of a connection. The duration is defined as a period since a connection + * was established. If not set, there is no max duration. When max_connection_duration is reached + * and if there are no active streams, the connection will be closed. If there are any active streams, + * the drain sequence will kick-in, and the connection will be force-closed after the drain period. + * See :ref:`drain_timeout + * `. + * Note: This feature is not yet implemented for the upstream connections. + */ + 'max_connection_duration': (_google_protobuf_Duration__Output | null); + /** + * Total duration to keep alive an HTTP request/response stream. If the time limit is reached the stream will be + * reset independent of any other timeouts. If not specified, this value is not set. + */ + 'max_stream_duration': (_google_protobuf_Duration__Output | null); + /** + * Action to take when a client request with a header name containing underscore characters is received. + * If this setting is not specified, the value defaults to ALLOW. + * Note: upstream responses are not affected by this setting. + */ + 'headers_with_underscores_action': (keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); + /** + * Optional maximum requests for both upstream and downstream connections. + * If not specified, there is no limit. + * Setting this parameter to 1 will effectively disable keep alive. + * For HTTP/2 and HTTP/3, due to concurrent stream processing, the limit is approximate. + */ + 'max_requests_per_connection': (_google_protobuf_UInt32Value__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts new file mode 100644 index 000000000..0bac9ac51 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts @@ -0,0 +1,79 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/http_uri.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * Envoy external URI descriptor + */ +export interface HttpUri { + /** + * The HTTP server URI. It should be a full FQDN with protocol, host and path. + * + * Example: + * + * .. code-block:: yaml + * + * uri: https://www.googleapis.com/oauth2/v1/certs + */ + 'uri'?: (string); + /** + * A cluster is created in the Envoy "cluster_manager" config + * section. This field specifies the cluster name. + * + * Example: + * + * .. code-block:: yaml + * + * cluster: jwks_cluster + */ + 'cluster'?: (string); + /** + * Sets the maximum duration in milliseconds that a response can take to arrive upon request. + */ + 'timeout'?: (_google_protobuf_Duration | null); + /** + * Specify how `uri` is to be fetched. Today, this requires an explicit + * cluster, but in the future we may support dynamic cluster creation or + * inline DNS resolution. See `issue + * `_. + */ + 'http_upstream_type'?: "cluster"; +} + +/** + * Envoy external URI descriptor + */ +export interface HttpUri__Output { + /** + * The HTTP server URI. It should be a full FQDN with protocol, host and path. + * + * Example: + * + * .. code-block:: yaml + * + * uri: https://www.googleapis.com/oauth2/v1/certs + */ + 'uri': (string); + /** + * A cluster is created in the Envoy "cluster_manager" config + * section. This field specifies the cluster name. + * + * Example: + * + * .. code-block:: yaml + * + * cluster: jwks_cluster + */ + 'cluster'?: (string); + /** + * Sets the maximum duration in milliseconds that a response can take to arrive upon request. + */ + 'timeout': (_google_protobuf_Duration__Output | null); + /** + * Specify how `uri` is to be fetched. Today, this requires an explicit + * cluster, but in the future we may support dynamic cluster creation or + * inline DNS resolution. See `issue + * `_. + */ + 'http_upstream_type': "cluster"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts new file mode 100644 index 000000000..2c274c6e1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts @@ -0,0 +1,58 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; + +export interface KeepaliveSettings { + /** + * Send HTTP/2 PING frames at this period, in order to test that the connection is still alive. + * If this is zero, interval PINGs will not be sent. + */ + 'interval'?: (_google_protobuf_Duration | null); + /** + * How long to wait for a response to a keepalive PING. If a response is not received within this + * time period, the connection will be aborted. + */ + 'timeout'?: (_google_protobuf_Duration | null); + /** + * A random jitter amount as a percentage of interval that will be added to each interval. + * A value of zero means there will be no jitter. + * The default value is 15%. + */ + 'interval_jitter'?: (_envoy_type_v3_Percent | null); + /** + * If the connection has been idle for this duration, send a HTTP/2 ping ahead + * of new stream creation, to quickly detect dead connections. + * If this is zero, this type of PING will not be sent. + * If an interval ping is outstanding, a second ping will not be sent as the + * interval ping will determine if the connection is dead. + */ + 'connection_idle_interval'?: (_google_protobuf_Duration | null); +} + +export interface KeepaliveSettings__Output { + /** + * Send HTTP/2 PING frames at this period, in order to test that the connection is still alive. + * If this is zero, interval PINGs will not be sent. + */ + 'interval': (_google_protobuf_Duration__Output | null); + /** + * How long to wait for a response to a keepalive PING. If a response is not received within this + * time period, the connection will be aborted. + */ + 'timeout': (_google_protobuf_Duration__Output | null); + /** + * A random jitter amount as a percentage of interval that will be added to each interval. + * A value of zero means there will be no jitter. + * The default value is 15%. + */ + 'interval_jitter': (_envoy_type_v3_Percent__Output | null); + /** + * If the connection has been idle for this duration, send a HTTP/2 ping ahead + * of new stream creation, to quickly detect dead connections. + * If this is zero, this type of PING will not be sent. + * If an interval ping is outstanding, a second ping will not be sent as the + * interval ping will determine if the connection is dead. + */ + 'connection_idle_interval': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts new file mode 100644 index 000000000..b15b53832 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts @@ -0,0 +1,56 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Identifies location of where either Envoy runs or where upstream hosts run. + */ +export interface Locality { + /** + * Region this :ref:`zone ` belongs to. + */ + 'region'?: (string); + /** + * Defines the local service zone where Envoy is running. Though optional, it + * should be set if discovery service routing is used and the discovery + * service exposes :ref:`zone data `, + * either in this message or via :option:`--service-zone`. The meaning of zone + * is context dependent, e.g. `Availability Zone (AZ) + * `_ + * on AWS, `Zone `_ on + * GCP, etc. + */ + 'zone'?: (string); + /** + * When used for locality of upstream hosts, this field further splits zone + * into smaller chunks of sub-zones so they can be load balanced + * independently. + */ + 'sub_zone'?: (string); +} + +/** + * Identifies location of where either Envoy runs or where upstream hosts run. + */ +export interface Locality__Output { + /** + * Region this :ref:`zone ` belongs to. + */ + 'region': (string); + /** + * Defines the local service zone where Envoy is running. Though optional, it + * should be set if discovery service routing is used and the discovery + * service exposes :ref:`zone data `, + * either in this message or via :option:`--service-zone`. The meaning of zone + * is context dependent, e.g. `Availability Zone (AZ) + * `_ + * on AWS, `Zone `_ on + * GCP, etc. + */ + 'zone': (string); + /** + * When used for locality of upstream hosts, this field further splits zone + * into smaller chunks of sub-zones so they can be load balanced + * independently. + */ + 'sub_zone': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts new file mode 100644 index 000000000..fb603c2ba --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts @@ -0,0 +1,94 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Metadata provides additional inputs to filters based on matched listeners, + * filter chains, routes and endpoints. It is structured as a map, usually from + * filter name (in reverse DNS format) to metadata specific to the filter. Metadata + * key-values for a filter are merged as connection and request handling occurs, + * with later values for the same key overriding earlier values. + * + * An example use of metadata is providing additional values to + * http_connection_manager in the envoy.http_connection_manager.access_log + * namespace. + * + * Another example use of metadata is to per service config info in cluster metadata, which may get + * consumed by multiple filters. + * + * For load balancing, Metadata provides a means to subset cluster endpoints. + * Endpoints have a Metadata object associated and routes contain a Metadata + * object to match against. There are some well defined metadata used today for + * this purpose: + * + * * ``{"envoy.lb": {"canary": }}`` This indicates the canary status of an + * endpoint and is also used during header processing + * (x-envoy-upstream-canary) and for stats purposes. + * [#next-major-version: move to type/metadata/v2] + */ +export interface Metadata { + /** + * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * namespace is reserved for Envoy's built-in filters. + * If both *filter_metadata* and + * :ref:`typed_filter_metadata ` + * fields are present in the metadata with same keys, + * only *typed_filter_metadata* field will be parsed. + */ + 'filter_metadata'?: ({[key: string]: _google_protobuf_Struct}); + /** + * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * namespace is reserved for Envoy's built-in filters. + * The value is encoded as google.protobuf.Any. + * If both :ref:`filter_metadata ` + * and *typed_filter_metadata* fields are present in the metadata with same keys, + * only *typed_filter_metadata* field will be parsed. + */ + 'typed_filter_metadata'?: ({[key: string]: _google_protobuf_Any}); +} + +/** + * Metadata provides additional inputs to filters based on matched listeners, + * filter chains, routes and endpoints. It is structured as a map, usually from + * filter name (in reverse DNS format) to metadata specific to the filter. Metadata + * key-values for a filter are merged as connection and request handling occurs, + * with later values for the same key overriding earlier values. + * + * An example use of metadata is providing additional values to + * http_connection_manager in the envoy.http_connection_manager.access_log + * namespace. + * + * Another example use of metadata is to per service config info in cluster metadata, which may get + * consumed by multiple filters. + * + * For load balancing, Metadata provides a means to subset cluster endpoints. + * Endpoints have a Metadata object associated and routes contain a Metadata + * object to match against. There are some well defined metadata used today for + * this purpose: + * + * * ``{"envoy.lb": {"canary": }}`` This indicates the canary status of an + * endpoint and is also used during header processing + * (x-envoy-upstream-canary) and for stats purposes. + * [#next-major-version: move to type/metadata/v2] + */ +export interface Metadata__Output { + /** + * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * namespace is reserved for Envoy's built-in filters. + * If both *filter_metadata* and + * :ref:`typed_filter_metadata ` + * fields are present in the metadata with same keys, + * only *typed_filter_metadata* field will be parsed. + */ + 'filter_metadata': ({[key: string]: _google_protobuf_Struct__Output}); + /** + * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * namespace is reserved for Envoy's built-in filters. + * The value is encoded as google.protobuf.Any. + * If both :ref:`filter_metadata ` + * and *typed_filter_metadata* fields are present in the metadata with same keys, + * only *typed_filter_metadata* field will be parsed. + */ + 'typed_filter_metadata': ({[key: string]: _google_protobuf_Any__Output}); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts new file mode 100644 index 000000000..addd47a68 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts @@ -0,0 +1,176 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { Locality as _envoy_config_core_v3_Locality, Locality__Output as _envoy_config_core_v3_Locality__Output } from '../../../../envoy/config/core/v3/Locality'; +import type { BuildVersion as _envoy_config_core_v3_BuildVersion, BuildVersion__Output as _envoy_config_core_v3_BuildVersion__Output } from '../../../../envoy/config/core/v3/BuildVersion'; +import type { Extension as _envoy_config_core_v3_Extension, Extension__Output as _envoy_config_core_v3_Extension__Output } from '../../../../envoy/config/core/v3/Extension'; +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { ContextParams as _xds_core_v3_ContextParams, ContextParams__Output as _xds_core_v3_ContextParams__Output } from '../../../../xds/core/v3/ContextParams'; + +/** + * Identifies a specific Envoy instance. The node identifier is presented to the + * management server, which may use this identifier to distinguish per Envoy + * configuration for serving. + * [#next-free-field: 13] + */ +export interface Node { + /** + * An opaque node identifier for the Envoy node. This also provides the local + * service node name. It should be set if any of the following features are + * used: :ref:`statsd `, :ref:`CDS + * `, and :ref:`HTTP tracing + * `, either in this message or via + * :option:`--service-node`. + */ + 'id'?: (string); + /** + * Defines the local service cluster name where Envoy is running. Though + * optional, it should be set if any of the following features are used: + * :ref:`statsd `, :ref:`health check cluster + * verification + * `, + * :ref:`runtime override directory `, + * :ref:`user agent addition + * `, + * :ref:`HTTP global rate limiting `, + * :ref:`CDS `, and :ref:`HTTP tracing + * `, either in this message or via + * :option:`--service-cluster`. + */ + 'cluster'?: (string); + /** + * Opaque metadata extending the node identifier. Envoy will pass this + * directly to the management server. + */ + 'metadata'?: (_google_protobuf_Struct | null); + /** + * Locality specifying where the Envoy instance is running. + */ + 'locality'?: (_envoy_config_core_v3_Locality | null); + /** + * Free-form string that identifies the entity requesting config. + * E.g. "envoy" or "grpc" + */ + 'user_agent_name'?: (string); + /** + * Free-form string that identifies the version of the entity requesting config. + * E.g. "1.12.2" or "abcd1234", or "SpecialEnvoyBuild" + */ + 'user_agent_version'?: (string); + /** + * Structured version of the entity requesting config. + */ + 'user_agent_build_version'?: (_envoy_config_core_v3_BuildVersion | null); + /** + * List of extensions and their versions supported by the node. + */ + 'extensions'?: (_envoy_config_core_v3_Extension)[]; + /** + * Client feature support list. These are well known features described + * in the Envoy API repository for a given major version of an API. Client features + * use reverse DNS naming scheme, for example `com.acme.feature`. + * See :ref:`the list of features ` that xDS client may + * support. + */ + 'client_features'?: (string)[]; + /** + * Known listening ports on the node as a generic hint to the management server + * for filtering :ref:`listeners ` to be returned. For example, + * if there is a listener bound to port 80, the list can optionally contain the + * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. + */ + 'listening_addresses'?: (_envoy_config_core_v3_Address)[]; + /** + * Map from xDS resource type URL to dynamic context parameters. These may vary at runtime (unlike + * other fields in this message). For example, the xDS client may have a shard identifier that + * changes during the lifetime of the xDS client. In Envoy, this would be achieved by updating the + * dynamic context on the Server::Instance's LocalInfo context provider. The shard ID dynamic + * parameter then appears in this field during future discovery requests. + */ + 'dynamic_parameters'?: ({[key: string]: _xds_core_v3_ContextParams}); + 'user_agent_version_type'?: "user_agent_version"|"user_agent_build_version"; +} + +/** + * Identifies a specific Envoy instance. The node identifier is presented to the + * management server, which may use this identifier to distinguish per Envoy + * configuration for serving. + * [#next-free-field: 13] + */ +export interface Node__Output { + /** + * An opaque node identifier for the Envoy node. This also provides the local + * service node name. It should be set if any of the following features are + * used: :ref:`statsd `, :ref:`CDS + * `, and :ref:`HTTP tracing + * `, either in this message or via + * :option:`--service-node`. + */ + 'id': (string); + /** + * Defines the local service cluster name where Envoy is running. Though + * optional, it should be set if any of the following features are used: + * :ref:`statsd `, :ref:`health check cluster + * verification + * `, + * :ref:`runtime override directory `, + * :ref:`user agent addition + * `, + * :ref:`HTTP global rate limiting `, + * :ref:`CDS `, and :ref:`HTTP tracing + * `, either in this message or via + * :option:`--service-cluster`. + */ + 'cluster': (string); + /** + * Opaque metadata extending the node identifier. Envoy will pass this + * directly to the management server. + */ + 'metadata': (_google_protobuf_Struct__Output | null); + /** + * Locality specifying where the Envoy instance is running. + */ + 'locality': (_envoy_config_core_v3_Locality__Output | null); + /** + * Free-form string that identifies the entity requesting config. + * E.g. "envoy" or "grpc" + */ + 'user_agent_name': (string); + /** + * Free-form string that identifies the version of the entity requesting config. + * E.g. "1.12.2" or "abcd1234", or "SpecialEnvoyBuild" + */ + 'user_agent_version'?: (string); + /** + * Structured version of the entity requesting config. + */ + 'user_agent_build_version'?: (_envoy_config_core_v3_BuildVersion__Output | null); + /** + * List of extensions and their versions supported by the node. + */ + 'extensions': (_envoy_config_core_v3_Extension__Output)[]; + /** + * Client feature support list. These are well known features described + * in the Envoy API repository for a given major version of an API. Client features + * use reverse DNS naming scheme, for example `com.acme.feature`. + * See :ref:`the list of features ` that xDS client may + * support. + */ + 'client_features': (string)[]; + /** + * Known listening ports on the node as a generic hint to the management server + * for filtering :ref:`listeners ` to be returned. For example, + * if there is a listener bound to port 80, the list can optionally contain the + * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. + */ + 'listening_addresses': (_envoy_config_core_v3_Address__Output)[]; + /** + * Map from xDS resource type URL to dynamic context parameters. These may vary at runtime (unlike + * other fields in this message). For example, the xDS client may have a shard identifier that + * changes during the lifetime of the xDS client. In Envoy, this would be achieved by updating the + * dynamic context on the Server::Instance's LocalInfo context provider. The shard ID dynamic + * parameter then appears in this field during future discovery requests. + */ + 'dynamic_parameters': ({[key: string]: _xds_core_v3_ContextParams__Output}); + 'user_agent_version_type': "user_agent_version"|"user_agent_build_version"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Pipe.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Pipe.ts new file mode 100644 index 000000000..3d8fdb1c8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Pipe.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + + +export interface Pipe { + /** + * Unix Domain Socket path. On Linux, paths starting with '@' will use the + * abstract namespace. The starting '@' is replaced by a null byte by Envoy. + * Paths starting with '@' will result in an error in environments other than + * Linux. + */ + 'path'?: (string); + /** + * The mode for the Pipe. Not applicable for abstract sockets. + */ + 'mode'?: (number); +} + +export interface Pipe__Output { + /** + * Unix Domain Socket path. On Linux, paths starting with '@' will use the + * abstract namespace. The starting '@' is replaced by a null byte by Envoy. + * Paths starting with '@' will result in an error in environments other than + * Linux. + */ + 'path': (string); + /** + * The mode for the Pipe. Not applicable for abstract sockets. + */ + 'mode': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts new file mode 100644 index 000000000..a28de2dbe --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts @@ -0,0 +1,29 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto + + +// Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto + +export enum _envoy_config_core_v3_ProxyProtocolConfig_Version { + /** + * PROXY protocol version 1. Human readable format. + */ + V1 = 0, + /** + * PROXY protocol version 2. Binary format. + */ + V2 = 1, +} + +export interface ProxyProtocolConfig { + /** + * The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details + */ + 'version'?: (_envoy_config_core_v3_ProxyProtocolConfig_Version | keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version); +} + +export interface ProxyProtocolConfig__Output { + /** + * The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details + */ + 'version': (keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QueryParameter.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QueryParameter.ts new file mode 100644 index 000000000..4cf7952fb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QueryParameter.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Query parameter name/value pair. + */ +export interface QueryParameter { + /** + * The key of the query parameter. Case sensitive. + */ + 'key'?: (string); + /** + * The value of the query parameter. + */ + 'value'?: (string); +} + +/** + * Query parameter name/value pair. + */ +export interface QueryParameter__Output { + /** + * The key of the query parameter. Case sensitive. + */ + 'key': (string); + /** + * The value of the query parameter. + */ + 'value': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts new file mode 100644 index 000000000..6a653b54d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts @@ -0,0 +1,69 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +/** + * QUIC protocol options which apply to both downstream and upstream connections. + */ +export interface QuicProtocolOptions { + /** + * Maximum number of streams that the client can negotiate per connection. 100 + * if not specified. + */ + 'max_concurrent_streams'?: (_google_protobuf_UInt32Value | null); + /** + * `Initial stream-level flow-control receive window + * `_ size. Valid values range from + * 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 65536 (2^16). + * + * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. If configured smaller than it, we will use 16384 instead. + * QUICHE IETF Quic implementation supports 1 bytes window. We only support increasing the default window size now, so it's also the minimum. + * + * This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + * QUIC stream send and receive buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + * stop the flow of data to the stream buffers. + */ + 'initial_stream_window_size'?: (_google_protobuf_UInt32Value | null); + /** + * Similar to *initial_stream_window_size*, but for connection-level + * flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). + * window. Currently, this has the same minimum/default as *initial_stream_window_size*. + * + * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default + * window size now, so it's also the minimum. + */ + 'initial_connection_window_size'?: (_google_protobuf_UInt32Value | null); +} + +/** + * QUIC protocol options which apply to both downstream and upstream connections. + */ +export interface QuicProtocolOptions__Output { + /** + * Maximum number of streams that the client can negotiate per connection. 100 + * if not specified. + */ + 'max_concurrent_streams': (_google_protobuf_UInt32Value__Output | null); + /** + * `Initial stream-level flow-control receive window + * `_ size. Valid values range from + * 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 65536 (2^16). + * + * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. If configured smaller than it, we will use 16384 instead. + * QUICHE IETF Quic implementation supports 1 bytes window. We only support increasing the default window size now, so it's also the minimum. + * + * This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + * QUIC stream send and receive buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + * stop the flow of data to the stream buffers. + */ + 'initial_stream_window_size': (_google_protobuf_UInt32Value__Output | null); + /** + * Similar to *initial_stream_window_size*, but for connection-level + * flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). + * window. Currently, this has the same minimum/default as *initial_stream_window_size*. + * + * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default + * window size now, so it's also the minimum. + */ + 'initial_connection_window_size': (_google_protobuf_UInt32Value__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts new file mode 100644 index 000000000..bf002d99c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { DoubleValue as _google_protobuf_DoubleValue, DoubleValue__Output as _google_protobuf_DoubleValue__Output } from '../../../../google/protobuf/DoubleValue'; + +/** + * Rate Limit settings to be applied for discovery requests made by Envoy. + */ +export interface RateLimitSettings { + /** + * Maximum number of tokens to be used for rate limiting discovery request calls. If not set, a + * default value of 100 will be used. + */ + 'max_tokens'?: (_google_protobuf_UInt32Value | null); + /** + * Rate at which tokens will be filled per second. If not set, a default fill rate of 10 tokens + * per second will be used. + */ + 'fill_rate'?: (_google_protobuf_DoubleValue | null); +} + +/** + * Rate Limit settings to be applied for discovery requests made by Envoy. + */ +export interface RateLimitSettings__Output { + /** + * Maximum number of tokens to be used for rate limiting discovery request calls. If not set, a + * default value of 100 will be used. + */ + 'max_tokens': (_google_protobuf_UInt32Value__Output | null); + /** + * Rate at which tokens will be filled per second. If not set, a default fill rate of 10 tokens + * per second will be used. + */ + 'fill_rate': (_google_protobuf_DoubleValue__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts new file mode 100644 index 000000000..917304d97 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts @@ -0,0 +1,40 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { HttpUri as _envoy_config_core_v3_HttpUri, HttpUri__Output as _envoy_config_core_v3_HttpUri__Output } from '../../../../envoy/config/core/v3/HttpUri'; +import type { RetryPolicy as _envoy_config_core_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_core_v3_RetryPolicy__Output } from '../../../../envoy/config/core/v3/RetryPolicy'; + +/** + * The message specifies how to fetch data from remote and how to verify it. + */ +export interface RemoteDataSource { + /** + * The HTTP URI to fetch the remote data. + */ + 'http_uri'?: (_envoy_config_core_v3_HttpUri | null); + /** + * SHA256 string for verifying data. + */ + 'sha256'?: (string); + /** + * Retry policy for fetching remote data. + */ + 'retry_policy'?: (_envoy_config_core_v3_RetryPolicy | null); +} + +/** + * The message specifies how to fetch data from remote and how to verify it. + */ +export interface RemoteDataSource__Output { + /** + * The HTTP URI to fetch the remote data. + */ + 'http_uri': (_envoy_config_core_v3_HttpUri__Output | null); + /** + * SHA256 string for verifying data. + */ + 'sha256': (string); + /** + * Retry policy for fetching remote data. + */ + 'retry_policy': (_envoy_config_core_v3_RetryPolicy__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts new file mode 100644 index 000000000..9be1aa6d1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +/** + * HTTP request method. + */ +export enum RequestMethod { + METHOD_UNSPECIFIED = 0, + GET = 1, + HEAD = 2, + POST = 3, + PUT = 4, + DELETE = 5, + CONNECT = 6, + OPTIONS = 7, + TRACE = 8, + PATCH = 9, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts new file mode 100644 index 000000000..6e2af23e6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts @@ -0,0 +1,38 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { BackoffStrategy as _envoy_config_core_v3_BackoffStrategy, BackoffStrategy__Output as _envoy_config_core_v3_BackoffStrategy__Output } from '../../../../envoy/config/core/v3/BackoffStrategy'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +/** + * The message specifies the retry policy of remote data source when fetching fails. + */ +export interface RetryPolicy { + /** + * Specifies parameters that control :ref:`retry backoff strategy `. + * This parameter is optional, in which case the default base interval is 1000 milliseconds. The + * default maximum interval is 10 times the base interval. + */ + 'retry_back_off'?: (_envoy_config_core_v3_BackoffStrategy | null); + /** + * Specifies the allowed number of retries. This parameter is optional and + * defaults to 1. + */ + 'num_retries'?: (_google_protobuf_UInt32Value | null); +} + +/** + * The message specifies the retry policy of remote data source when fetching fails. + */ +export interface RetryPolicy__Output { + /** + * Specifies parameters that control :ref:`retry backoff strategy `. + * This parameter is optional, in which case the default base interval is 1000 milliseconds. The + * default maximum interval is 10 times the base interval. + */ + 'retry_back_off': (_envoy_config_core_v3_BackoffStrategy__Output | null); + /** + * Specifies the allowed number of retries. This parameter is optional and + * defaults to 1. + */ + 'num_retries': (_google_protobuf_UInt32Value__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts new file mode 100644 index 000000000..917d8a3df --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts @@ -0,0 +1,15 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +/** + * Envoy supports :ref:`upstream priority routing + * ` both at the route and the virtual + * cluster level. The current priority implementation uses different connection + * pool and circuit breaking settings for each priority level. This means that + * even for HTTP/2 requests, two physical connections will be used to an + * upstream host. In the future Envoy will likely support true HTTP/2 priority + * over a single upstream connection. + */ +export enum RoutingPriority { + DEFAULT = 0, + HIGH = 1, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeDouble.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeDouble.ts new file mode 100644 index 000000000..a0f849ab5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeDouble.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Runtime derived double with a default when not specified. + */ +export interface RuntimeDouble { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (number | string); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived double with a default when not specified. + */ +export interface RuntimeDouble__Output { + /** + * Default value if runtime value is not available. + */ + 'default_value': (number); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts new file mode 100644 index 000000000..b6df8d617 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; + +/** + * Runtime derived bool with a default when not specified. + */ +export interface RuntimeFeatureFlag { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (_google_protobuf_BoolValue | null); + /** + * Runtime key to get value for comparison. This value is used if defined. The boolean value must + * be represented via its + * `canonical JSON encoding `_. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived bool with a default when not specified. + */ +export interface RuntimeFeatureFlag__Output { + /** + * Default value if runtime value is not available. + */ + 'default_value': (_google_protobuf_BoolValue__Output | null); + /** + * Runtime key to get value for comparison. This value is used if defined. The boolean value must + * be represented via its + * `canonical JSON encoding `_. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts new file mode 100644 index 000000000..3a2073294 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts @@ -0,0 +1,49 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; + +/** + * Runtime derived FractionalPercent with defaults for when the numerator or denominator is not + * specified via a runtime key. + * + * .. note:: + * + * Parsing of the runtime key's data is implemented such that it may be represented as a + * :ref:`FractionalPercent ` proto represented as JSON/YAML + * and may also be represented as an integer with the assumption that the value is an integral + * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse + * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. + */ +export interface RuntimeFractionalPercent { + /** + * Default value if the runtime value's for the numerator/denominator keys are not available. + */ + 'default_value'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Runtime key for a YAML representation of a FractionalPercent. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived FractionalPercent with defaults for when the numerator or denominator is not + * specified via a runtime key. + * + * .. note:: + * + * Parsing of the runtime key's data is implemented such that it may be represented as a + * :ref:`FractionalPercent ` proto represented as JSON/YAML + * and may also be represented as an integer with the assumption that the value is an integral + * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse + * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. + */ +export interface RuntimeFractionalPercent__Output { + /** + * Default value if the runtime value's for the numerator/denominator keys are not available. + */ + 'default_value': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Runtime key for a YAML representation of a FractionalPercent. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts new file mode 100644 index 000000000..1dbe6ea4a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts @@ -0,0 +1,31 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; + +/** + * Runtime derived percentage with a default when not specified. + */ +export interface RuntimePercent { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (_envoy_type_v3_Percent | null); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived percentage with a default when not specified. + */ +export interface RuntimePercent__Output { + /** + * Default value if runtime value is not available. + */ + 'default_value': (_envoy_type_v3_Percent__Output | null); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeUInt32.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeUInt32.ts new file mode 100644 index 000000000..6cc9eead6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeUInt32.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Runtime derived uint32 with a default when not specified. + */ +export interface RuntimeUInt32 { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (number); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived uint32 with a default when not specified. + */ +export interface RuntimeUInt32__Output { + /** + * Default value if runtime value is not available. + */ + 'default_value': (number); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SchemeHeaderTransformation.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SchemeHeaderTransformation.ts new file mode 100644 index 000000000..95bb4e400 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SchemeHeaderTransformation.ts @@ -0,0 +1,24 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + + +/** + * A message to control transformations to the :scheme header + */ +export interface SchemeHeaderTransformation { + /** + * Overwrite any Scheme header with the contents of this string. + */ + 'scheme_to_overwrite'?: (string); + 'transformation'?: "scheme_to_overwrite"; +} + +/** + * A message to control transformations to the :scheme header + */ +export interface SchemeHeaderTransformation__Output { + /** + * Overwrite any Scheme header with the contents of this string. + */ + 'scheme_to_overwrite'?: (string); + 'transformation': "scheme_to_overwrite"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts new file mode 100644 index 000000000..3912fd1cf --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts @@ -0,0 +1,31 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + +import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; + +/** + * [#not-implemented-hide:] + * Self-referencing config source options. This is currently empty, but when + * set in :ref:`ConfigSource ` can be used to + * specify that other data can be obtained from the same server. + */ +export interface SelfConfigSource { + /** + * API version for xDS transport protocol. This describes the xDS gRPC/REST + * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + */ + 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); +} + +/** + * [#not-implemented-hide:] + * Self-referencing config source options. This is currently empty, but when + * set in :ref:`ConfigSource ` can be used to + * specify that other data can be obtained from the same server. + */ +export interface SelfConfigSource__Output { + /** + * API version for xDS transport protocol. This describes the xDS gRPC/REST + * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + */ + 'transport_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts new file mode 100644 index 000000000..3966dc04c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts @@ -0,0 +1,97 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + + +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +export enum _envoy_config_core_v3_SocketAddress_Protocol { + TCP = 0, + UDP = 1, +} + +/** + * [#next-free-field: 7] + */ +export interface SocketAddress { + 'protocol'?: (_envoy_config_core_v3_SocketAddress_Protocol | keyof typeof _envoy_config_core_v3_SocketAddress_Protocol); + /** + * The address for this socket. :ref:`Listeners ` will bind + * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` + * to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: + * It is possible to distinguish a Listener address via the prefix/suffix matching + * in :ref:`FilterChainMatch `.] When used + * within an upstream :ref:`BindConfig `, the address + * controls the source address of outbound connections. For :ref:`clusters + * `, the cluster type determines whether the + * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS + * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized + * via :ref:`resolver_name `. + */ + 'address'?: (string); + 'port_value'?: (number); + /** + * This is only valid if :ref:`resolver_name + * ` is specified below and the + * named resolver is capable of named port resolution. + */ + 'named_port'?: (string); + /** + * The name of the custom resolver. This must have been registered with Envoy. If + * this is empty, a context dependent default applies. If the address is a concrete + * IP address, no resolution will occur. If address is a hostname this + * should be set for resolution other than DNS. Specifying a custom resolver with + * *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. + */ + 'resolver_name'?: (string); + /** + * When binding to an IPv6 address above, this enables `IPv4 compatibility + * `_. Binding to ``::`` will + * allow both IPv4 and IPv6 connections, with peer IPv4 addresses mapped into + * IPv6 space as ``::FFFF:``. + */ + 'ipv4_compat'?: (boolean); + 'port_specifier'?: "port_value"|"named_port"; +} + +/** + * [#next-free-field: 7] + */ +export interface SocketAddress__Output { + 'protocol': (keyof typeof _envoy_config_core_v3_SocketAddress_Protocol); + /** + * The address for this socket. :ref:`Listeners ` will bind + * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` + * to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: + * It is possible to distinguish a Listener address via the prefix/suffix matching + * in :ref:`FilterChainMatch `.] When used + * within an upstream :ref:`BindConfig `, the address + * controls the source address of outbound connections. For :ref:`clusters + * `, the cluster type determines whether the + * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS + * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized + * via :ref:`resolver_name `. + */ + 'address': (string); + 'port_value'?: (number); + /** + * This is only valid if :ref:`resolver_name + * ` is specified below and the + * named resolver is capable of named port resolution. + */ + 'named_port'?: (string); + /** + * The name of the custom resolver. This must have been registered with Envoy. If + * this is empty, a context dependent default applies. If the address is a concrete + * IP address, no resolution will occur. If address is a hostname this + * should be set for resolution other than DNS. Specifying a custom resolver with + * *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. + */ + 'resolver_name': (string); + /** + * When binding to an IPv6 address above, this enables `IPv4 compatibility + * `_. Binding to ``::`` will + * allow both IPv4 and IPv6 connections, with peer IPv4 addresses mapped into + * IPv6 space as ``::FFFF:``. + */ + 'ipv4_compat': (boolean); + 'port_specifier': "port_value"|"named_port"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts new file mode 100644 index 000000000..56f13c339 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts @@ -0,0 +1,90 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/socket_option.proto + +import type { Long } from '@grpc/proto-loader'; + +// Original file: deps/envoy-api/envoy/config/core/v3/socket_option.proto + +export enum _envoy_config_core_v3_SocketOption_SocketState { + /** + * Socket options are applied after socket creation but before binding the socket to a port + */ + STATE_PREBIND = 0, + /** + * Socket options are applied after binding the socket to a port but before calling listen() + */ + STATE_BOUND = 1, + /** + * Socket options are applied after calling listen() + */ + STATE_LISTENING = 2, +} + +/** + * Generic socket option message. This would be used to set socket options that + * might not exist in upstream kernels or precompiled Envoy binaries. + * [#next-free-field: 7] + */ +export interface SocketOption { + /** + * An optional name to give this socket option for debugging, etc. + * Uniqueness is not required and no special meaning is assumed. + */ + 'description'?: (string); + /** + * Corresponding to the level value passed to setsockopt, such as IPPROTO_TCP + */ + 'level'?: (number | string | Long); + /** + * The numeric name as passed to setsockopt + */ + 'name'?: (number | string | Long); + /** + * Because many sockopts take an int value. + */ + 'int_value'?: (number | string | Long); + /** + * Otherwise it's a byte buffer. + */ + 'buf_value'?: (Buffer | Uint8Array | string); + /** + * The state in which the option will be applied. When used in BindConfig + * STATE_PREBIND is currently the only valid value. + */ + 'state'?: (_envoy_config_core_v3_SocketOption_SocketState | keyof typeof _envoy_config_core_v3_SocketOption_SocketState); + 'value'?: "int_value"|"buf_value"; +} + +/** + * Generic socket option message. This would be used to set socket options that + * might not exist in upstream kernels or precompiled Envoy binaries. + * [#next-free-field: 7] + */ +export interface SocketOption__Output { + /** + * An optional name to give this socket option for debugging, etc. + * Uniqueness is not required and no special meaning is assumed. + */ + 'description': (string); + /** + * Corresponding to the level value passed to setsockopt, such as IPPROTO_TCP + */ + 'level': (string); + /** + * The numeric name as passed to setsockopt + */ + 'name': (string); + /** + * Because many sockopts take an int value. + */ + 'int_value'?: (string); + /** + * Otherwise it's a byte buffer. + */ + 'buf_value'?: (Buffer); + /** + * The state in which the option will be applied. When used in BindConfig + * STATE_PREBIND is currently the only valid value. + */ + 'state': (keyof typeof _envoy_config_core_v3_SocketOption_SocketState); + 'value': "int_value"|"buf_value"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts new file mode 100644 index 000000000..a935fc1ec --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts @@ -0,0 +1,199 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/substitution_format_string.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Configuration to use multiple :ref:`command operators ` + * to generate a new string in either plain text or JSON format. + * [#next-free-field: 7] + */ +export interface SubstitutionFormatString { + /** + * Specify a format with command operators to form a text string. + * Its details is described in :ref:`format string`. + * + * For example, setting ``text_format`` like below, + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * generates plain text similar to: + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + * + * Deprecated in favor of :ref:`text_format_source `. To migrate text format strings, use the :ref:`inline_string ` field. + */ + 'text_format'?: (string); + /** + * Specify a format with command operators to form a JSON string. + * Its details is described in :ref:`format dictionary`. + * Values are rendered as strings, numbers, or boolean values as appropriate. + * Nested JSON objects may be produced by some command operators (e.g. FILTER_STATE or DYNAMIC_METADATA). + * See the documentation for a specific command operator for details. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * json_format: + * status: "%RESPONSE_CODE%" + * message: "%LOCAL_REPLY_BODY%" + * + * The following JSON object would be created: + * + * .. code-block:: json + * + * { + * "status": 500, + * "message": "My error message" + * } + */ + 'json_format'?: (_google_protobuf_Struct | null); + /** + * If set to true, when command operators are evaluated to null, + * + * * for ``text_format``, the output of the empty operator is changed from ``-`` to an + * empty string, so that empty values are omitted entirely. + * * for ``json_format`` the keys with null values are omitted in the output structure. + */ + 'omit_empty_values'?: (boolean); + /** + * Specify a *content_type* field. + * If this field is not set then ``text/plain`` is used for *text_format* and + * ``application/json`` is used for *json_format*. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * content_type: "text/html; charset=UTF-8" + */ + 'content_type'?: (string); + /** + * Specify a format with command operators to form a text string. + * Its details is described in :ref:`format string`. + * + * For example, setting ``text_format`` like below, + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format_source: + * inline_string: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * generates plain text similar to: + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + */ + 'text_format_source'?: (_envoy_config_core_v3_DataSource | null); + /** + * Specifies a collection of Formatter plugins that can be called from the access log configuration. + * See the formatters extensions documentation for details. + * [#extension-category: envoy.formatter] + */ + 'formatters'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + 'format'?: "text_format"|"json_format"|"text_format_source"; +} + +/** + * Configuration to use multiple :ref:`command operators ` + * to generate a new string in either plain text or JSON format. + * [#next-free-field: 7] + */ +export interface SubstitutionFormatString__Output { + /** + * Specify a format with command operators to form a text string. + * Its details is described in :ref:`format string`. + * + * For example, setting ``text_format`` like below, + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * generates plain text similar to: + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + * + * Deprecated in favor of :ref:`text_format_source `. To migrate text format strings, use the :ref:`inline_string ` field. + */ + 'text_format'?: (string); + /** + * Specify a format with command operators to form a JSON string. + * Its details is described in :ref:`format dictionary`. + * Values are rendered as strings, numbers, or boolean values as appropriate. + * Nested JSON objects may be produced by some command operators (e.g. FILTER_STATE or DYNAMIC_METADATA). + * See the documentation for a specific command operator for details. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * json_format: + * status: "%RESPONSE_CODE%" + * message: "%LOCAL_REPLY_BODY%" + * + * The following JSON object would be created: + * + * .. code-block:: json + * + * { + * "status": 500, + * "message": "My error message" + * } + */ + 'json_format'?: (_google_protobuf_Struct__Output | null); + /** + * If set to true, when command operators are evaluated to null, + * + * * for ``text_format``, the output of the empty operator is changed from ``-`` to an + * empty string, so that empty values are omitted entirely. + * * for ``json_format`` the keys with null values are omitted in the output structure. + */ + 'omit_empty_values': (boolean); + /** + * Specify a *content_type* field. + * If this field is not set then ``text/plain`` is used for *text_format* and + * ``application/json`` is used for *json_format*. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * content_type: "text/html; charset=UTF-8" + */ + 'content_type': (string); + /** + * Specify a format with command operators to form a text string. + * Its details is described in :ref:`format string`. + * + * For example, setting ``text_format`` like below, + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format_source: + * inline_string: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * generates plain text similar to: + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + */ + 'text_format_source'?: (_envoy_config_core_v3_DataSource__Output | null); + /** + * Specifies a collection of Formatter plugins that can be called from the access log configuration. + * See the formatters extensions documentation for details. + * [#extension-category: envoy.formatter] + */ + 'formatters': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + 'format': "text_format"|"json_format"|"text_format_source"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts new file mode 100644 index 000000000..1ad81091d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +export interface TcpKeepalive { + /** + * Maximum number of keepalive probes to send without response before deciding + * the connection is dead. Default is to use the OS level configuration (unless + * overridden, Linux defaults to 9.) + */ + 'keepalive_probes'?: (_google_protobuf_UInt32Value | null); + /** + * The number of seconds a connection needs to be idle before keep-alive probes + * start being sent. Default is to use the OS level configuration (unless + * overridden, Linux defaults to 7200s (i.e., 2 hours.) + */ + 'keepalive_time'?: (_google_protobuf_UInt32Value | null); + /** + * The number of seconds between keep-alive probes. Default is to use the OS + * level configuration (unless overridden, Linux defaults to 75s.) + */ + 'keepalive_interval'?: (_google_protobuf_UInt32Value | null); +} + +export interface TcpKeepalive__Output { + /** + * Maximum number of keepalive probes to send without response before deciding + * the connection is dead. Default is to use the OS level configuration (unless + * overridden, Linux defaults to 9.) + */ + 'keepalive_probes': (_google_protobuf_UInt32Value__Output | null); + /** + * The number of seconds a connection needs to be idle before keep-alive probes + * start being sent. Default is to use the OS level configuration (unless + * overridden, Linux defaults to 7200s (i.e., 2 hours.) + */ + 'keepalive_time': (_google_protobuf_UInt32Value__Output | null); + /** + * The number of seconds between keep-alive probes. Default is to use the OS + * level configuration (unless overridden, Linux defaults to 75s.) + */ + 'keepalive_interval': (_google_protobuf_UInt32Value__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpProtocolOptions.ts new file mode 100644 index 000000000..28239f63e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpProtocolOptions.ts @@ -0,0 +1,14 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + + +/** + * [#not-implemented-hide:] + */ +export interface TcpProtocolOptions { +} + +/** + * [#not-implemented-hide:] + */ +export interface TcpProtocolOptions__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts new file mode 100644 index 000000000..b68323b09 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts @@ -0,0 +1,19 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +/** + * Identifies the direction of the traffic relative to the local Envoy. + */ +export enum TrafficDirection { + /** + * Default option is unspecified. + */ + UNSPECIFIED = 0, + /** + * The transport is used for incoming traffic. + */ + INBOUND = 1, + /** + * The transport is used for outgoing traffic. + */ + OUTBOUND = 2, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts new file mode 100644 index 000000000..ff05991ad --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Configuration for transport socket in :ref:`listeners ` and + * :ref:`clusters `. If the configuration is + * empty, a default transport socket implementation and configuration will be + * chosen based on the platform and existence of tls_context. + */ +export interface TransportSocket { + /** + * The name of the transport socket to instantiate. The name must match a supported transport + * socket implementation. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Implementation specific configuration which depends on the implementation being instantiated. + * See the supported transport socket implementations for further documentation. + */ + 'config_type'?: "typed_config"; +} + +/** + * Configuration for transport socket in :ref:`listeners ` and + * :ref:`clusters `. If the configuration is + * empty, a default transport socket implementation and configuration will be + * chosen based on the platform and existence of tls_context. + */ +export interface TransportSocket__Output { + /** + * The name of the transport socket to instantiate. The name must match a supported transport + * socket implementation. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Implementation specific configuration which depends on the implementation being instantiated. + * See the supported transport socket implementations for further documentation. + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts new file mode 100644 index 000000000..d653f9373 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/extension.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Message type for extension configuration. + * [#next-major-version: revisit all existing typed_config that doesn't use this wrapper.]. + */ +export interface TypedExtensionConfig { + /** + * The name of an extension. This is not used to select the extension, instead + * it serves the role of an opaque identifier. + */ + 'name'?: (string); + /** + * The typed config for the extension. The type URL will be used to identify + * the extension. In the case that the type URL is *udpa.type.v1.TypedStruct*, + * the inner type URL of *TypedStruct* will be utilized. See the + * :ref:`extension configuration overview + * ` for further details. + */ + 'typed_config'?: (_google_protobuf_Any | null); +} + +/** + * Message type for extension configuration. + * [#next-major-version: revisit all existing typed_config that doesn't use this wrapper.]. + */ +export interface TypedExtensionConfig__Output { + /** + * The name of an extension. This is not used to select the extension, instead + * it serves the role of an opaque identifier. + */ + 'name': (string); + /** + * The typed config for the extension. The type URL will be used to identify + * the extension. In the case that the type URL is *udpa.type.v1.TypedStruct*, + * the inner type URL of *TypedStruct* will be utilized. See the + * :ref:`extension configuration overview + * ` for further details. + */ + 'typed_config': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts new file mode 100644 index 000000000..fe1b038db --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts @@ -0,0 +1,45 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/udp_socket_config.proto + +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Generic UDP socket configuration. + */ +export interface UdpSocketConfig { + /** + * The maximum size of received UDP datagrams. Using a larger size will cause Envoy to allocate + * more memory per socket. Received datagrams above this size will be dropped. If not set + * defaults to 1500 bytes. + */ + 'max_rx_datagram_size'?: (_google_protobuf_UInt64Value | null); + /** + * Configures whether Generic Receive Offload (GRO) + * _ is preferred when reading from the + * UDP socket. The default is context dependent and is documented where UdpSocketConfig is used. + * This option affects performance but not functionality. If GRO is not supported by the operating + * system, non-GRO receive will be used. + */ + 'prefer_gro'?: (_google_protobuf_BoolValue | null); +} + +/** + * Generic UDP socket configuration. + */ +export interface UdpSocketConfig__Output { + /** + * The maximum size of received UDP datagrams. Using a larger size will cause Envoy to allocate + * more memory per socket. Received datagrams above this size will be dropped. If not set + * defaults to 1500 bytes. + */ + 'max_rx_datagram_size': (_google_protobuf_UInt64Value__Output | null); + /** + * Configures whether Generic Receive Offload (GRO) + * _ is preferred when reading from the + * UDP socket. The default is context dependent and is documented where UdpSocketConfig is used. + * This option affects performance but not functionality. If GRO is not supported by the operating + * system, non-GRO receive will be used. + */ + 'prefer_gro': (_google_protobuf_BoolValue__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts new file mode 100644 index 000000000..c0da4159f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts @@ -0,0 +1,56 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + + +export interface UpstreamHttpProtocolOptions { + /** + * Set transport socket `SNI `_ for new + * upstream connections based on the downstream HTTP host/authority header or any other arbitrary + * header when :ref:`override_auto_sni_header ` + * is set, as seen by the :ref:`router filter `. + */ + 'auto_sni'?: (boolean); + /** + * Automatic validate upstream presented certificate for new upstream connections based on the + * downstream HTTP host/authority header or any other arbitrary header when :ref:`override_auto_sni_header ` + * is set, as seen by the :ref:`router filter `. + * This field is intended to be set with `auto_sni` field. + */ + 'auto_san_validation'?: (boolean); + /** + * An optional alternative to the host/authority header to be used for setting the SNI value. + * It should be a valid downstream HTTP header, as seen by the + * :ref:`router filter `. + * If unset, host/authority header will be used for populating the SNI. If the specified header + * is not found or the value is empty, host/authority header will be used instead. + * This field is intended to be set with `auto_sni` and/or `auto_san_validation` fields. + * If none of these fields are set then setting this would be a no-op. + */ + 'override_auto_sni_header'?: (string); +} + +export interface UpstreamHttpProtocolOptions__Output { + /** + * Set transport socket `SNI `_ for new + * upstream connections based on the downstream HTTP host/authority header or any other arbitrary + * header when :ref:`override_auto_sni_header ` + * is set, as seen by the :ref:`router filter `. + */ + 'auto_sni': (boolean); + /** + * Automatic validate upstream presented certificate for new upstream connections based on the + * downstream HTTP host/authority header or any other arbitrary header when :ref:`override_auto_sni_header ` + * is set, as seen by the :ref:`router filter `. + * This field is intended to be set with `auto_sni` field. + */ + 'auto_san_validation': (boolean); + /** + * An optional alternative to the host/authority header to be used for setting the SNI value. + * It should be a valid downstream HTTP header, as seen by the + * :ref:`router filter `. + * If unset, host/authority header will be used for populating the SNI. If the specified header + * is not found or the value is empty, host/authority header will be used instead. + * This field is intended to be set with `auto_sni` and/or `auto_san_validation` fields. + * If none of these fields are set then setting this would be a no-op. + */ + 'override_auto_sni_header': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/WatchedDirectory.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/WatchedDirectory.ts new file mode 100644 index 000000000..d6f0d124b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/WatchedDirectory.ts @@ -0,0 +1,24 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * A directory that is watched for changes, e.g. by inotify on Linux. Move/rename + * events inside this directory trigger the watch. + */ +export interface WatchedDirectory { + /** + * Directory path to watch. + */ + 'path'?: (string); +} + +/** + * A directory that is watched for changes, e.g. by inotify on Linux. Move/rename + * events inside this directory trigger the watch. + */ +export interface WatchedDirectory__Output { + /** + * Directory path to watch. + */ + 'path': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts new file mode 100644 index 000000000..91ce2e0c8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts @@ -0,0 +1,213 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint.proto + +import type { LocalityLbEndpoints as _envoy_config_endpoint_v3_LocalityLbEndpoints, LocalityLbEndpoints__Output as _envoy_config_endpoint_v3_LocalityLbEndpoints__Output } from '../../../../envoy/config/endpoint/v3/LocalityLbEndpoints'; +import type { Endpoint as _envoy_config_endpoint_v3_Endpoint, Endpoint__Output as _envoy_config_endpoint_v3_Endpoint__Output } from '../../../../envoy/config/endpoint/v3/Endpoint'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; + +/** + * [#not-implemented-hide:] + */ +export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload { + /** + * Identifier for the policy specifying the drop. + */ + 'category'?: (string); + /** + * Percentage of traffic that should be dropped for the category. + */ + 'drop_percentage'?: (_envoy_type_v3_FractionalPercent | null); +} + +/** + * [#not-implemented-hide:] + */ +export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload__Output { + /** + * Identifier for the policy specifying the drop. + */ + 'category': (string); + /** + * Percentage of traffic that should be dropped for the category. + */ + 'drop_percentage': (_envoy_type_v3_FractionalPercent__Output | null); +} + +/** + * Load balancing policy settings. + * [#next-free-field: 6] + */ +export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy { + /** + * Action to trim the overall incoming traffic to protect the upstream + * hosts. This action allows protection in case the hosts are unable to + * recover from an outage, or unable to autoscale or unable to handle + * incoming traffic volume for any reason. + * + * At the client each category is applied one after the other to generate + * the 'actual' drop percentage on all outgoing traffic. For example: + * + * .. code-block:: json + * + * { "drop_overloads": [ + * { "category": "throttle", "drop_percentage": 60 } + * { "category": "lb", "drop_percentage": 50 } + * ]} + * + * The actual drop percentages applied to the traffic at the clients will be + * "throttle"_drop = 60% + * "lb"_drop = 20% // 50% of the remaining 'actual' load, which is 40%. + * actual_outgoing_load = 20% // remaining after applying all categories. + * [#not-implemented-hide:] + */ + 'drop_overloads'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload)[]; + /** + * Priority levels and localities are considered overprovisioned with this + * factor (in percentage). This means that we don't consider a priority + * level or locality unhealthy until the fraction of healthy hosts + * multiplied by the overprovisioning factor drops below 100. + * With the default value 140(1.4), Envoy doesn't consider a priority level + * or a locality unhealthy until their percentage of healthy hosts drops + * below 72%. For example: + * + * .. code-block:: json + * + * { "overprovisioning_factor": 100 } + * + * Read more at :ref:`priority levels ` and + * :ref:`localities `. + */ + 'overprovisioning_factor'?: (_google_protobuf_UInt32Value | null); + /** + * The max time until which the endpoints from this assignment can be used. + * If no new assignments are received before this time expires the endpoints + * are considered stale and should be marked unhealthy. + * Defaults to 0 which means endpoints never go stale. + */ + 'endpoint_stale_after'?: (_google_protobuf_Duration | null); +} + +/** + * Load balancing policy settings. + * [#next-free-field: 6] + */ +export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy__Output { + /** + * Action to trim the overall incoming traffic to protect the upstream + * hosts. This action allows protection in case the hosts are unable to + * recover from an outage, or unable to autoscale or unable to handle + * incoming traffic volume for any reason. + * + * At the client each category is applied one after the other to generate + * the 'actual' drop percentage on all outgoing traffic. For example: + * + * .. code-block:: json + * + * { "drop_overloads": [ + * { "category": "throttle", "drop_percentage": 60 } + * { "category": "lb", "drop_percentage": 50 } + * ]} + * + * The actual drop percentages applied to the traffic at the clients will be + * "throttle"_drop = 60% + * "lb"_drop = 20% // 50% of the remaining 'actual' load, which is 40%. + * actual_outgoing_load = 20% // remaining after applying all categories. + * [#not-implemented-hide:] + */ + 'drop_overloads': (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload__Output)[]; + /** + * Priority levels and localities are considered overprovisioned with this + * factor (in percentage). This means that we don't consider a priority + * level or locality unhealthy until the fraction of healthy hosts + * multiplied by the overprovisioning factor drops below 100. + * With the default value 140(1.4), Envoy doesn't consider a priority level + * or a locality unhealthy until their percentage of healthy hosts drops + * below 72%. For example: + * + * .. code-block:: json + * + * { "overprovisioning_factor": 100 } + * + * Read more at :ref:`priority levels ` and + * :ref:`localities `. + */ + 'overprovisioning_factor': (_google_protobuf_UInt32Value__Output | null); + /** + * The max time until which the endpoints from this assignment can be used. + * If no new assignments are received before this time expires the endpoints + * are considered stale and should be marked unhealthy. + * Defaults to 0 which means endpoints never go stale. + */ + 'endpoint_stale_after': (_google_protobuf_Duration__Output | null); +} + +/** + * Each route from RDS will map to a single cluster or traffic split across + * clusters using weights expressed in the RDS WeightedCluster. + * + * With EDS, each cluster is treated independently from a LB perspective, with + * LB taking place between the Localities within a cluster and at a finer + * granularity between the hosts within a locality. The percentage of traffic + * for each endpoint is determined by both its load_balancing_weight, and the + * load_balancing_weight of its locality. First, a locality will be selected, + * then an endpoint within that locality will be chose based on its weight. + * [#next-free-field: 6] + */ +export interface ClusterLoadAssignment { + /** + * Name of the cluster. This will be the :ref:`service_name + * ` value if specified + * in the cluster :ref:`EdsClusterConfig + * `. + */ + 'cluster_name'?: (string); + /** + * List of endpoints to load balance to. + */ + 'endpoints'?: (_envoy_config_endpoint_v3_LocalityLbEndpoints)[]; + /** + * Load balancing policy settings. + */ + 'policy'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy | null); + /** + * Map of named endpoints that can be referenced in LocalityLbEndpoints. + * [#not-implemented-hide:] + */ + 'named_endpoints'?: ({[key: string]: _envoy_config_endpoint_v3_Endpoint}); +} + +/** + * Each route from RDS will map to a single cluster or traffic split across + * clusters using weights expressed in the RDS WeightedCluster. + * + * With EDS, each cluster is treated independently from a LB perspective, with + * LB taking place between the Localities within a cluster and at a finer + * granularity between the hosts within a locality. The percentage of traffic + * for each endpoint is determined by both its load_balancing_weight, and the + * load_balancing_weight of its locality. First, a locality will be selected, + * then an endpoint within that locality will be chose based on its weight. + * [#next-free-field: 6] + */ +export interface ClusterLoadAssignment__Output { + /** + * Name of the cluster. This will be the :ref:`service_name + * ` value if specified + * in the cluster :ref:`EdsClusterConfig + * `. + */ + 'cluster_name': (string); + /** + * List of endpoints to load balance to. + */ + 'endpoints': (_envoy_config_endpoint_v3_LocalityLbEndpoints__Output)[]; + /** + * Load balancing policy settings. + */ + 'policy': (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy__Output | null); + /** + * Map of named endpoints that can be referenced in LocalityLbEndpoints. + * [#not-implemented-hide:] + */ + 'named_endpoints': ({[key: string]: _envoy_config_endpoint_v3_Endpoint__Output}); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts new file mode 100644 index 000000000..c160333b2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts @@ -0,0 +1,115 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/load_report.proto + +import type { UpstreamLocalityStats as _envoy_config_endpoint_v3_UpstreamLocalityStats, UpstreamLocalityStats__Output as _envoy_config_endpoint_v3_UpstreamLocalityStats__Output } from '../../../../envoy/config/endpoint/v3/UpstreamLocalityStats'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Long } from '@grpc/proto-loader'; + +export interface _envoy_config_endpoint_v3_ClusterStats_DroppedRequests { + /** + * Identifier for the policy specifying the drop. + */ + 'category'?: (string); + /** + * Total number of deliberately dropped requests for the category. + */ + 'dropped_count'?: (number | string | Long); +} + +export interface _envoy_config_endpoint_v3_ClusterStats_DroppedRequests__Output { + /** + * Identifier for the policy specifying the drop. + */ + 'category': (string); + /** + * Total number of deliberately dropped requests for the category. + */ + 'dropped_count': (string); +} + +/** + * Per cluster load stats. Envoy reports these stats a management server in a + * :ref:`LoadStatsRequest` + * Next ID: 7 + * [#next-free-field: 7] + */ +export interface ClusterStats { + /** + * The name of the cluster. + */ + 'cluster_name'?: (string); + /** + * Need at least one. + */ + 'upstream_locality_stats'?: (_envoy_config_endpoint_v3_UpstreamLocalityStats)[]; + /** + * Cluster-level stats such as total_successful_requests may be computed by + * summing upstream_locality_stats. In addition, below there are additional + * cluster-wide stats. + * + * The total number of dropped requests. This covers requests + * deliberately dropped by the drop_overload policy and circuit breaking. + */ + 'total_dropped_requests'?: (number | string | Long); + /** + * Period over which the actual load report occurred. This will be guaranteed to include every + * request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy + * and the *LoadStatsResponse* message sent from the management server, this may be longer than + * the requested load reporting interval in the *LoadStatsResponse*. + */ + 'load_report_interval'?: (_google_protobuf_Duration | null); + /** + * Information about deliberately dropped requests for each category specified + * in the DropOverload policy. + */ + 'dropped_requests'?: (_envoy_config_endpoint_v3_ClusterStats_DroppedRequests)[]; + /** + * The eds_cluster_config service_name of the cluster. + * It's possible that two clusters send the same service_name to EDS, + * in that case, the management server is supposed to do aggregation on the load reports. + */ + 'cluster_service_name'?: (string); +} + +/** + * Per cluster load stats. Envoy reports these stats a management server in a + * :ref:`LoadStatsRequest` + * Next ID: 7 + * [#next-free-field: 7] + */ +export interface ClusterStats__Output { + /** + * The name of the cluster. + */ + 'cluster_name': (string); + /** + * Need at least one. + */ + 'upstream_locality_stats': (_envoy_config_endpoint_v3_UpstreamLocalityStats__Output)[]; + /** + * Cluster-level stats such as total_successful_requests may be computed by + * summing upstream_locality_stats. In addition, below there are additional + * cluster-wide stats. + * + * The total number of dropped requests. This covers requests + * deliberately dropped by the drop_overload policy and circuit breaking. + */ + 'total_dropped_requests': (string); + /** + * Period over which the actual load report occurred. This will be guaranteed to include every + * request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy + * and the *LoadStatsResponse* message sent from the management server, this may be longer than + * the requested load reporting interval in the *LoadStatsResponse*. + */ + 'load_report_interval': (_google_protobuf_Duration__Output | null); + /** + * Information about deliberately dropped requests for each category specified + * in the DropOverload policy. + */ + 'dropped_requests': (_envoy_config_endpoint_v3_ClusterStats_DroppedRequests__Output)[]; + /** + * The eds_cluster_config service_name of the cluster. + * It's possible that two clusters send the same service_name to EDS, + * in that case, the management server is supposed to do aggregation on the load reports. + */ + 'cluster_service_name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts new file mode 100644 index 000000000..31eb09055 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts @@ -0,0 +1,119 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; + +/** + * The optional health check configuration. + */ +export interface _envoy_config_endpoint_v3_Endpoint_HealthCheckConfig { + /** + * Optional alternative health check port value. + * + * By default the health check address port of an upstream host is the same + * as the host's serving address port. This provides an alternative health + * check port. Setting this with a non-zero value allows an upstream host + * to have different health check address port. + */ + 'port_value'?: (number); + /** + * By default, the host header for L7 health checks is controlled by cluster level configuration + * (see: :ref:`host ` and + * :ref:`authority `). Setting this + * to a non-empty value allows overriding the cluster level configuration for a specific + * endpoint. + */ + 'hostname'?: (string); +} + +/** + * The optional health check configuration. + */ +export interface _envoy_config_endpoint_v3_Endpoint_HealthCheckConfig__Output { + /** + * Optional alternative health check port value. + * + * By default the health check address port of an upstream host is the same + * as the host's serving address port. This provides an alternative health + * check port. Setting this with a non-zero value allows an upstream host + * to have different health check address port. + */ + 'port_value': (number); + /** + * By default, the host header for L7 health checks is controlled by cluster level configuration + * (see: :ref:`host ` and + * :ref:`authority `). Setting this + * to a non-empty value allows overriding the cluster level configuration for a specific + * endpoint. + */ + 'hostname': (string); +} + +/** + * Upstream host identifier. + */ +export interface Endpoint { + /** + * The upstream host address. + * + * .. attention:: + * + * The form of host address depends on the given cluster type. For STATIC or EDS, + * it is expected to be a direct IP address (or something resolvable by the + * specified :ref:`resolver ` + * in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, + * and will be resolved via DNS. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * The optional health check configuration is used as configuration for the + * health checker to contact the health checked host. + * + * .. attention:: + * + * This takes into effect only for upstream clusters with + * :ref:`active health checking ` enabled. + */ + 'health_check_config'?: (_envoy_config_endpoint_v3_Endpoint_HealthCheckConfig | null); + /** + * The hostname associated with this endpoint. This hostname is not used for routing or address + * resolution. If provided, it will be associated with the endpoint, and can be used for features + * that require a hostname, like + * :ref:`auto_host_rewrite `. + */ + 'hostname'?: (string); +} + +/** + * Upstream host identifier. + */ +export interface Endpoint__Output { + /** + * The upstream host address. + * + * .. attention:: + * + * The form of host address depends on the given cluster type. For STATIC or EDS, + * it is expected to be a direct IP address (or something resolvable by the + * specified :ref:`resolver ` + * in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, + * and will be resolved via DNS. + */ + 'address': (_envoy_config_core_v3_Address__Output | null); + /** + * The optional health check configuration is used as configuration for the + * health checker to contact the health checked host. + * + * .. attention:: + * + * This takes into effect only for upstream clusters with + * :ref:`active health checking ` enabled. + */ + 'health_check_config': (_envoy_config_endpoint_v3_Endpoint_HealthCheckConfig__Output | null); + /** + * The hostname associated with this endpoint. This hostname is not used for routing or address + * resolution. If provided, it will be associated with the endpoint, and can be used for features + * that require a hostname, like + * :ref:`auto_host_rewrite `. + */ + 'hostname': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/EndpointLoadMetricStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/EndpointLoadMetricStats.ts new file mode 100644 index 000000000..50f63f4ca --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/EndpointLoadMetricStats.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/load_report.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface EndpointLoadMetricStats { + /** + * Name of the metric; may be empty. + */ + 'metric_name'?: (string); + /** + * Number of calls that finished and included this metric. + */ + 'num_requests_finished_with_metric'?: (number | string | Long); + /** + * Sum of metric values across all calls that finished with this metric for + * load_reporting_interval. + */ + 'total_metric_value'?: (number | string); +} + +export interface EndpointLoadMetricStats__Output { + /** + * Name of the metric; may be empty. + */ + 'metric_name': (string); + /** + * Number of calls that finished and included this metric. + */ + 'num_requests_finished_with_metric': (string); + /** + * Sum of metric values across all calls that finished with this metric for + * load_reporting_interval. + */ + 'total_metric_value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts new file mode 100644 index 000000000..6025ae3a7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts @@ -0,0 +1,90 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto + +import type { Endpoint as _envoy_config_endpoint_v3_Endpoint, Endpoint__Output as _envoy_config_endpoint_v3_Endpoint__Output } from '../../../../envoy/config/endpoint/v3/Endpoint'; +import type { HealthStatus as _envoy_config_core_v3_HealthStatus } from '../../../../envoy/config/core/v3/HealthStatus'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +/** + * An Endpoint that Envoy can route traffic to. + * [#next-free-field: 6] + */ +export interface LbEndpoint { + 'endpoint'?: (_envoy_config_endpoint_v3_Endpoint | null); + /** + * Optional health status when known and supplied by EDS server. + */ + 'health_status'?: (_envoy_config_core_v3_HealthStatus | keyof typeof _envoy_config_core_v3_HealthStatus); + /** + * The endpoint metadata specifies values that may be used by the load + * balancer to select endpoints in a cluster for a given request. The filter + * name should be specified as *envoy.lb*. An example boolean key-value pair + * is *canary*, providing the optional canary status of the upstream host. + * This may be matched against in a route's + * :ref:`RouteAction ` metadata_match field + * to subset the endpoints considered in cluster load balancing. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata | null); + /** + * The optional load balancing weight of the upstream host; at least 1. + * Envoy uses the load balancing weight in some of the built in load + * balancers. The load balancing weight for an endpoint is divided by the sum + * of the weights of all endpoints in the endpoint's locality to produce a + * percentage of traffic for the endpoint. This percentage is then further + * weighted by the endpoint's locality's load balancing weight from + * LocalityLbEndpoints. If unspecified, each host is presumed to have equal + * weight in a locality. The sum of the weights of all endpoints in the + * endpoint's locality must not exceed uint32_t maximal value (4294967295). + */ + 'load_balancing_weight'?: (_google_protobuf_UInt32Value | null); + /** + * [#not-implemented-hide:] + */ + 'endpoint_name'?: (string); + /** + * Upstream host identifier or a named reference. + */ + 'host_identifier'?: "endpoint"|"endpoint_name"; +} + +/** + * An Endpoint that Envoy can route traffic to. + * [#next-free-field: 6] + */ +export interface LbEndpoint__Output { + 'endpoint'?: (_envoy_config_endpoint_v3_Endpoint__Output | null); + /** + * Optional health status when known and supplied by EDS server. + */ + 'health_status': (keyof typeof _envoy_config_core_v3_HealthStatus); + /** + * The endpoint metadata specifies values that may be used by the load + * balancer to select endpoints in a cluster for a given request. The filter + * name should be specified as *envoy.lb*. An example boolean key-value pair + * is *canary*, providing the optional canary status of the upstream host. + * This may be matched against in a route's + * :ref:`RouteAction ` metadata_match field + * to subset the endpoints considered in cluster load balancing. + */ + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); + /** + * The optional load balancing weight of the upstream host; at least 1. + * Envoy uses the load balancing weight in some of the built in load + * balancers. The load balancing weight for an endpoint is divided by the sum + * of the weights of all endpoints in the endpoint's locality to produce a + * percentage of traffic for the endpoint. This percentage is then further + * weighted by the endpoint's locality's load balancing weight from + * LocalityLbEndpoints. If unspecified, each host is presumed to have equal + * weight in a locality. The sum of the weights of all endpoints in the + * endpoint's locality must not exceed uint32_t maximal value (4294967295). + */ + 'load_balancing_weight': (_google_protobuf_UInt32Value__Output | null); + /** + * [#not-implemented-hide:] + */ + 'endpoint_name'?: (string); + /** + * Upstream host identifier or a named reference. + */ + 'host_identifier': "endpoint"|"endpoint_name"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LedsClusterLocalityConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LedsClusterLocalityConfig.ts new file mode 100644 index 000000000..1229d33a3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LedsClusterLocalityConfig.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; + +/** + * [#not-implemented-hide:] + * A configuration for a LEDS collection. + */ +export interface LedsClusterLocalityConfig { + /** + * Configuration for the source of LEDS updates for a Locality. + */ + 'leds_config'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * The xDS transport protocol glob collection resource name. + * The service is only supported in delta xDS (incremental) mode. + */ + 'leds_collection_name'?: (string); +} + +/** + * [#not-implemented-hide:] + * A configuration for a LEDS collection. + */ +export interface LedsClusterLocalityConfig__Output { + /** + * Configuration for the source of LEDS updates for a Locality. + */ + 'leds_config': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * The xDS transport protocol glob collection resource name. + * The service is only supported in delta xDS (incremental) mode. + */ + 'leds_collection_name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts new file mode 100644 index 000000000..182e27c9c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts @@ -0,0 +1,160 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto + +import type { Locality as _envoy_config_core_v3_Locality, Locality__Output as _envoy_config_core_v3_Locality__Output } from '../../../../envoy/config/core/v3/Locality'; +import type { LbEndpoint as _envoy_config_endpoint_v3_LbEndpoint, LbEndpoint__Output as _envoy_config_endpoint_v3_LbEndpoint__Output } from '../../../../envoy/config/endpoint/v3/LbEndpoint'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { LedsClusterLocalityConfig as _envoy_config_endpoint_v3_LedsClusterLocalityConfig, LedsClusterLocalityConfig__Output as _envoy_config_endpoint_v3_LedsClusterLocalityConfig__Output } from '../../../../envoy/config/endpoint/v3/LedsClusterLocalityConfig'; + +/** + * [#not-implemented-hide:] + * A list of endpoints of a specific locality. + */ +export interface _envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList { + 'lb_endpoints'?: (_envoy_config_endpoint_v3_LbEndpoint)[]; +} + +/** + * [#not-implemented-hide:] + * A list of endpoints of a specific locality. + */ +export interface _envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList__Output { + 'lb_endpoints': (_envoy_config_endpoint_v3_LbEndpoint__Output)[]; +} + +/** + * A group of endpoints belonging to a Locality. + * One can have multiple LocalityLbEndpoints for a locality, but this is + * generally only done if the different groups need to have different load + * balancing weights or different priorities. + * [#next-free-field: 9] + */ +export interface LocalityLbEndpoints { + /** + * Identifies location of where the upstream hosts run. + */ + 'locality'?: (_envoy_config_core_v3_Locality | null); + /** + * The group of endpoints belonging to the locality specified. + * [#comment:TODO(adisuissa): Once LEDS is implemented this field needs to be + * deprecated and replaced by *load_balancer_endpoints*.] + */ + 'lb_endpoints'?: (_envoy_config_endpoint_v3_LbEndpoint)[]; + /** + * Optional: Per priority/region/zone/sub_zone weight; at least 1. The load + * balancing weight for a locality is divided by the sum of the weights of all + * localities at the same priority level to produce the effective percentage + * of traffic for the locality. The sum of the weights of all localities at + * the same priority level must not exceed uint32_t maximal value (4294967295). + * + * Locality weights are only considered when :ref:`locality weighted load + * balancing ` is + * configured. These weights are ignored otherwise. If no weights are + * specified when locality weighted load balancing is enabled, the locality is + * assigned no load. + */ + 'load_balancing_weight'?: (_google_protobuf_UInt32Value | null); + /** + * Optional: the priority for this LocalityLbEndpoints. If unspecified this will + * default to the highest priority (0). + * + * Under usual circumstances, Envoy will only select endpoints for the highest + * priority (0). In the event all endpoints for a particular priority are + * unavailable/unhealthy, Envoy will fail over to selecting endpoints for the + * next highest priority group. + * + * Priorities should range from 0 (highest) to N (lowest) without skipping. + */ + 'priority'?: (number); + /** + * Optional: Per locality proximity value which indicates how close this + * locality is from the source locality. This value only provides ordering + * information (lower the value, closer it is to the source locality). + * This will be consumed by load balancing schemes that need proximity order + * to determine where to route the requests. + * [#not-implemented-hide:] + */ + 'proximity'?: (_google_protobuf_UInt32Value | null); + /** + * The group of endpoints belonging to the locality. + * [#comment:TODO(adisuissa): Once LEDS is implemented the *lb_endpoints* field + * needs to be deprecated.] + */ + 'load_balancer_endpoints'?: (_envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList | null); + /** + * LEDS Configuration for the current locality. + */ + 'leds_cluster_locality_config'?: (_envoy_config_endpoint_v3_LedsClusterLocalityConfig | null); + /** + * [#not-implemented-hide:] + */ + 'lb_config'?: "load_balancer_endpoints"|"leds_cluster_locality_config"; +} + +/** + * A group of endpoints belonging to a Locality. + * One can have multiple LocalityLbEndpoints for a locality, but this is + * generally only done if the different groups need to have different load + * balancing weights or different priorities. + * [#next-free-field: 9] + */ +export interface LocalityLbEndpoints__Output { + /** + * Identifies location of where the upstream hosts run. + */ + 'locality': (_envoy_config_core_v3_Locality__Output | null); + /** + * The group of endpoints belonging to the locality specified. + * [#comment:TODO(adisuissa): Once LEDS is implemented this field needs to be + * deprecated and replaced by *load_balancer_endpoints*.] + */ + 'lb_endpoints': (_envoy_config_endpoint_v3_LbEndpoint__Output)[]; + /** + * Optional: Per priority/region/zone/sub_zone weight; at least 1. The load + * balancing weight for a locality is divided by the sum of the weights of all + * localities at the same priority level to produce the effective percentage + * of traffic for the locality. The sum of the weights of all localities at + * the same priority level must not exceed uint32_t maximal value (4294967295). + * + * Locality weights are only considered when :ref:`locality weighted load + * balancing ` is + * configured. These weights are ignored otherwise. If no weights are + * specified when locality weighted load balancing is enabled, the locality is + * assigned no load. + */ + 'load_balancing_weight': (_google_protobuf_UInt32Value__Output | null); + /** + * Optional: the priority for this LocalityLbEndpoints. If unspecified this will + * default to the highest priority (0). + * + * Under usual circumstances, Envoy will only select endpoints for the highest + * priority (0). In the event all endpoints for a particular priority are + * unavailable/unhealthy, Envoy will fail over to selecting endpoints for the + * next highest priority group. + * + * Priorities should range from 0 (highest) to N (lowest) without skipping. + */ + 'priority': (number); + /** + * Optional: Per locality proximity value which indicates how close this + * locality is from the source locality. This value only provides ordering + * information (lower the value, closer it is to the source locality). + * This will be consumed by load balancing schemes that need proximity order + * to determine where to route the requests. + * [#not-implemented-hide:] + */ + 'proximity': (_google_protobuf_UInt32Value__Output | null); + /** + * The group of endpoints belonging to the locality. + * [#comment:TODO(adisuissa): Once LEDS is implemented the *lb_endpoints* field + * needs to be deprecated.] + */ + 'load_balancer_endpoints'?: (_envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList__Output | null); + /** + * LEDS Configuration for the current locality. + */ + 'leds_cluster_locality_config'?: (_envoy_config_endpoint_v3_LedsClusterLocalityConfig__Output | null); + /** + * [#not-implemented-hide:] + */ + 'lb_config': "load_balancer_endpoints"|"leds_cluster_locality_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts new file mode 100644 index 000000000..ab06afb39 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts @@ -0,0 +1,104 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/load_report.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { EndpointLoadMetricStats as _envoy_config_endpoint_v3_EndpointLoadMetricStats, EndpointLoadMetricStats__Output as _envoy_config_endpoint_v3_EndpointLoadMetricStats__Output } from '../../../../envoy/config/endpoint/v3/EndpointLoadMetricStats'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { Long } from '@grpc/proto-loader'; + +/** + * [#next-free-field: 8] + */ +export interface UpstreamEndpointStats { + /** + * Upstream host address. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * The total number of requests successfully completed by the endpoints in the + * locality. These include non-5xx responses for HTTP, where errors + * originate at the client and the endpoint responded successfully. For gRPC, + * the grpc-status values are those not covered by total_error_requests below. + */ + 'total_successful_requests'?: (number | string | Long); + /** + * The total number of unfinished requests for this endpoint. + */ + 'total_requests_in_progress'?: (number | string | Long); + /** + * The total number of requests that failed due to errors at the endpoint. + * For HTTP these are responses with 5xx status codes and for gRPC the + * grpc-status values: + * + * - DeadlineExceeded + * - Unimplemented + * - Internal + * - Unavailable + * - Unknown + * - DataLoss + */ + 'total_error_requests'?: (number | string | Long); + /** + * Stats for multi-dimensional load balancing. + */ + 'load_metric_stats'?: (_envoy_config_endpoint_v3_EndpointLoadMetricStats)[]; + /** + * Opaque and implementation dependent metadata of the + * endpoint. Envoy will pass this directly to the management server. + */ + 'metadata'?: (_google_protobuf_Struct | null); + /** + * The total number of requests that were issued to this endpoint + * since the last report. A single TCP connection, HTTP or gRPC + * request or stream is counted as one request. + */ + 'total_issued_requests'?: (number | string | Long); +} + +/** + * [#next-free-field: 8] + */ +export interface UpstreamEndpointStats__Output { + /** + * Upstream host address. + */ + 'address': (_envoy_config_core_v3_Address__Output | null); + /** + * The total number of requests successfully completed by the endpoints in the + * locality. These include non-5xx responses for HTTP, where errors + * originate at the client and the endpoint responded successfully. For gRPC, + * the grpc-status values are those not covered by total_error_requests below. + */ + 'total_successful_requests': (string); + /** + * The total number of unfinished requests for this endpoint. + */ + 'total_requests_in_progress': (string); + /** + * The total number of requests that failed due to errors at the endpoint. + * For HTTP these are responses with 5xx status codes and for gRPC the + * grpc-status values: + * + * - DeadlineExceeded + * - Unimplemented + * - Internal + * - Unavailable + * - Unknown + * - DataLoss + */ + 'total_error_requests': (string); + /** + * Stats for multi-dimensional load balancing. + */ + 'load_metric_stats': (_envoy_config_endpoint_v3_EndpointLoadMetricStats__Output)[]; + /** + * Opaque and implementation dependent metadata of the + * endpoint. Envoy will pass this directly to the management server. + */ + 'metadata': (_google_protobuf_Struct__Output | null); + /** + * The total number of requests that were issued to this endpoint + * since the last report. A single TCP connection, HTTP or gRPC + * request or stream is counted as one request. + */ + 'total_issued_requests': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts new file mode 100644 index 000000000..fbfb05ed6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts @@ -0,0 +1,104 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/load_report.proto + +import type { Locality as _envoy_config_core_v3_Locality, Locality__Output as _envoy_config_core_v3_Locality__Output } from '../../../../envoy/config/core/v3/Locality'; +import type { EndpointLoadMetricStats as _envoy_config_endpoint_v3_EndpointLoadMetricStats, EndpointLoadMetricStats__Output as _envoy_config_endpoint_v3_EndpointLoadMetricStats__Output } from '../../../../envoy/config/endpoint/v3/EndpointLoadMetricStats'; +import type { UpstreamEndpointStats as _envoy_config_endpoint_v3_UpstreamEndpointStats, UpstreamEndpointStats__Output as _envoy_config_endpoint_v3_UpstreamEndpointStats__Output } from '../../../../envoy/config/endpoint/v3/UpstreamEndpointStats'; +import type { Long } from '@grpc/proto-loader'; + +/** + * These are stats Envoy reports to the management server at a frequency defined by + * :ref:`LoadStatsResponse.load_reporting_interval`. + * Stats per upstream region/zone and optionally per subzone. + * [#next-free-field: 9] + */ +export interface UpstreamLocalityStats { + /** + * Name of zone, region and optionally endpoint group these metrics were + * collected from. Zone and region names could be empty if unknown. + */ + 'locality'?: (_envoy_config_core_v3_Locality | null); + /** + * The total number of requests successfully completed by the endpoints in the + * locality. + */ + 'total_successful_requests'?: (number | string | Long); + /** + * The total number of unfinished requests + */ + 'total_requests_in_progress'?: (number | string | Long); + /** + * The total number of requests that failed due to errors at the endpoint, + * aggregated over all endpoints in the locality. + */ + 'total_error_requests'?: (number | string | Long); + /** + * Stats for multi-dimensional load balancing. + */ + 'load_metric_stats'?: (_envoy_config_endpoint_v3_EndpointLoadMetricStats)[]; + /** + * [#not-implemented-hide:] The priority of the endpoint group these metrics + * were collected from. + */ + 'priority'?: (number); + /** + * Endpoint granularity stats information for this locality. This information + * is populated if the Server requests it by setting + * :ref:`LoadStatsResponse.report_endpoint_granularity`. + */ + 'upstream_endpoint_stats'?: (_envoy_config_endpoint_v3_UpstreamEndpointStats)[]; + /** + * The total number of requests that were issued by this Envoy since + * the last report. This information is aggregated over all the + * upstream endpoints in the locality. + */ + 'total_issued_requests'?: (number | string | Long); +} + +/** + * These are stats Envoy reports to the management server at a frequency defined by + * :ref:`LoadStatsResponse.load_reporting_interval`. + * Stats per upstream region/zone and optionally per subzone. + * [#next-free-field: 9] + */ +export interface UpstreamLocalityStats__Output { + /** + * Name of zone, region and optionally endpoint group these metrics were + * collected from. Zone and region names could be empty if unknown. + */ + 'locality': (_envoy_config_core_v3_Locality__Output | null); + /** + * The total number of requests successfully completed by the endpoints in the + * locality. + */ + 'total_successful_requests': (string); + /** + * The total number of unfinished requests + */ + 'total_requests_in_progress': (string); + /** + * The total number of requests that failed due to errors at the endpoint, + * aggregated over all endpoints in the locality. + */ + 'total_error_requests': (string); + /** + * Stats for multi-dimensional load balancing. + */ + 'load_metric_stats': (_envoy_config_endpoint_v3_EndpointLoadMetricStats__Output)[]; + /** + * [#not-implemented-hide:] The priority of the endpoint group these metrics + * were collected from. + */ + 'priority': (number); + /** + * Endpoint granularity stats information for this locality. This information + * is populated if the Server requests it by setting + * :ref:`LoadStatsResponse.report_endpoint_granularity`. + */ + 'upstream_endpoint_stats': (_envoy_config_endpoint_v3_UpstreamEndpointStats__Output)[]; + /** + * The total number of requests that were issued by this Envoy since + * the last report. This information is aggregated over all the + * upstream endpoints in the locality. + */ + 'total_issued_requests': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ActiveRawUdpListenerConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ActiveRawUdpListenerConfig.ts new file mode 100644 index 000000000..3cc895aa9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ActiveRawUdpListenerConfig.ts @@ -0,0 +1,8 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/udp_listener_config.proto + + +export interface ActiveRawUdpListenerConfig { +} + +export interface ActiveRawUdpListenerConfig__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts new file mode 100644 index 000000000..5a8e7f37f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts @@ -0,0 +1,41 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/api_listener.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Describes a type of API listener, which is used in non-proxy clients. The type of API + * exposed to the non-proxy application depends on the type of API listener. + */ +export interface ApiListener { + /** + * The type in this field determines the type of API listener. At present, the following + * types are supported: + * envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager (HTTP) + * envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager (HTTP) + * [#next-major-version: In the v3 API, replace this Any field with a oneof containing the + * specific config message for each type of API listener. We could not do this in v2 because + * it would have caused circular dependencies for go protos: lds.proto depends on this file, + * and http_connection_manager.proto depends on rds.proto, which is in the same directory as + * lds.proto, so lds.proto cannot depend on this file.] + */ + 'api_listener'?: (_google_protobuf_Any | null); +} + +/** + * Describes a type of API listener, which is used in non-proxy clients. The type of API + * exposed to the non-proxy application depends on the type of API listener. + */ +export interface ApiListener__Output { + /** + * The type in this field determines the type of API listener. At present, the following + * types are supported: + * envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager (HTTP) + * envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager (HTTP) + * [#next-major-version: In the v3 API, replace this Any field with a oneof containing the + * specific config message for each type of API listener. We could not do this in v2 because + * it would have caused circular dependencies for go protos: lds.proto depends on this file, + * and http_connection_manager.proto depends on rds.proto, which is in the same directory as + * lds.proto, so lds.proto cannot depend on this file.] + */ + 'api_listener': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts new file mode 100644 index 000000000..66e903b28 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts @@ -0,0 +1,54 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { ExtensionConfigSource as _envoy_config_core_v3_ExtensionConfigSource, ExtensionConfigSource__Output as _envoy_config_core_v3_ExtensionConfigSource__Output } from '../../../../envoy/config/core/v3/ExtensionConfigSource'; + +/** + * [#next-free-field: 6] + */ +export interface Filter { + /** + * The name of the filter to instantiate. The name must match a + * :ref:`supported filter `. + */ + 'name'?: (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + * [#extension-category: envoy.filters.network] + */ + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Configuration source specifier for an extension configuration discovery + * service. In case of a failure and without the default configuration, the + * listener closes the connections. + * [#not-implemented-hide:] + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource | null); + 'config_type'?: "typed_config"|"config_discovery"; +} + +/** + * [#next-free-field: 6] + */ +export interface Filter__Output { + /** + * The name of the filter to instantiate. The name must match a + * :ref:`supported filter `. + */ + 'name': (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + * [#extension-category: envoy.filters.network] + */ + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Configuration source specifier for an extension configuration discovery + * service. In case of a failure and without the default configuration, the + * listener closes the connections. + * [#not-implemented-hide:] + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output | null); + 'config_type': "typed_config"|"config_discovery"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts new file mode 100644 index 000000000..e65f433c4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts @@ -0,0 +1,172 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto + +import type { FilterChainMatch as _envoy_config_listener_v3_FilterChainMatch, FilterChainMatch__Output as _envoy_config_listener_v3_FilterChainMatch__Output } from '../../../../envoy/config/listener/v3/FilterChainMatch'; +import type { Filter as _envoy_config_listener_v3_Filter, Filter__Output as _envoy_config_listener_v3_Filter__Output } from '../../../../envoy/config/listener/v3/Filter'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { TransportSocket as _envoy_config_core_v3_TransportSocket, TransportSocket__Output as _envoy_config_core_v3_TransportSocket__Output } from '../../../../envoy/config/core/v3/TransportSocket'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * The configuration for on-demand filter chain. If this field is not empty in FilterChain message, + * a filter chain will be built on-demand. + * On-demand filter chains help speedup the warming up of listeners since the building and initialization of + * an on-demand filter chain will be postponed to the arrival of new connection requests that require this filter chain. + * Filter chains that are not often used can be set as on-demand. + */ +export interface _envoy_config_listener_v3_FilterChain_OnDemandConfiguration { + /** + * The timeout to wait for filter chain placeholders to complete rebuilding. + * 1. If this field is set to 0, timeout is disabled. + * 2. If not specified, a default timeout of 15s is used. + * Rebuilding will wait until dependencies are ready, have failed, or this timeout is reached. + * Upon failure or timeout, all connections related to this filter chain will be closed. + * Rebuilding will start again on the next new connection. + */ + 'rebuild_timeout'?: (_google_protobuf_Duration | null); +} + +/** + * The configuration for on-demand filter chain. If this field is not empty in FilterChain message, + * a filter chain will be built on-demand. + * On-demand filter chains help speedup the warming up of listeners since the building and initialization of + * an on-demand filter chain will be postponed to the arrival of new connection requests that require this filter chain. + * Filter chains that are not often used can be set as on-demand. + */ +export interface _envoy_config_listener_v3_FilterChain_OnDemandConfiguration__Output { + /** + * The timeout to wait for filter chain placeholders to complete rebuilding. + * 1. If this field is set to 0, timeout is disabled. + * 2. If not specified, a default timeout of 15s is used. + * Rebuilding will wait until dependencies are ready, have failed, or this timeout is reached. + * Upon failure or timeout, all connections related to this filter chain will be closed. + * Rebuilding will start again on the next new connection. + */ + 'rebuild_timeout': (_google_protobuf_Duration__Output | null); +} + +/** + * A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and + * various other parameters. + * [#next-free-field: 10] + */ +export interface FilterChain { + /** + * The criteria to use when matching a connection to this filter chain. + */ + 'filter_chain_match'?: (_envoy_config_listener_v3_FilterChainMatch | null); + /** + * A list of individual network filters that make up the filter chain for + * connections established with the listener. Order matters as the filters are + * processed sequentially as connection events happen. Note: If the filter + * list is empty, the connection will close by default. + */ + 'filters'?: (_envoy_config_listener_v3_Filter)[]; + /** + * Whether the listener should expect a PROXY protocol V1 header on new + * connections. If this option is enabled, the listener will assume that that + * remote address of the connection is the one specified in the header. Some + * load balancers including the AWS ELB support this option. If the option is + * absent or set to false, Envoy will use the physical peer address of the + * connection as the remote address. + * + * This field is deprecated. Add a + * :ref:`PROXY protocol listener filter ` + * explicitly instead. + */ + 'use_proxy_proto'?: (_google_protobuf_BoolValue | null); + /** + * [#not-implemented-hide:] filter chain metadata. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata | null); + /** + * Optional custom transport socket implementation to use for downstream connections. + * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and + * :ref:`DownstreamTlsContext ` in the `typed_config`. + * If no transport socket configuration is specified, new connections + * will be set up with plaintext. + * [#extension-category: envoy.transport_sockets.downstream] + */ + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); + /** + * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no + * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter + * chain is to be dynamically updated or removed via FCDS a unique name must be provided. + */ + 'name'?: (string); + /** + * [#not-implemented-hide:] The configuration to specify whether the filter chain will be built on-demand. + * If this field is not empty, the filter chain will be built on-demand. + * Otherwise, the filter chain will be built normally and block listener warming. + */ + 'on_demand_configuration'?: (_envoy_config_listener_v3_FilterChain_OnDemandConfiguration | null); + /** + * If present and nonzero, the amount of time to allow incoming connections to complete any + * transport socket negotiations. If this expires before the transport reports connection + * establishment, the connection is summarily closed. + */ + 'transport_socket_connect_timeout'?: (_google_protobuf_Duration | null); +} + +/** + * A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and + * various other parameters. + * [#next-free-field: 10] + */ +export interface FilterChain__Output { + /** + * The criteria to use when matching a connection to this filter chain. + */ + 'filter_chain_match': (_envoy_config_listener_v3_FilterChainMatch__Output | null); + /** + * A list of individual network filters that make up the filter chain for + * connections established with the listener. Order matters as the filters are + * processed sequentially as connection events happen. Note: If the filter + * list is empty, the connection will close by default. + */ + 'filters': (_envoy_config_listener_v3_Filter__Output)[]; + /** + * Whether the listener should expect a PROXY protocol V1 header on new + * connections. If this option is enabled, the listener will assume that that + * remote address of the connection is the one specified in the header. Some + * load balancers including the AWS ELB support this option. If the option is + * absent or set to false, Envoy will use the physical peer address of the + * connection as the remote address. + * + * This field is deprecated. Add a + * :ref:`PROXY protocol listener filter ` + * explicitly instead. + */ + 'use_proxy_proto': (_google_protobuf_BoolValue__Output | null); + /** + * [#not-implemented-hide:] filter chain metadata. + */ + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); + /** + * Optional custom transport socket implementation to use for downstream connections. + * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and + * :ref:`DownstreamTlsContext ` in the `typed_config`. + * If no transport socket configuration is specified, new connections + * will be set up with plaintext. + * [#extension-category: envoy.transport_sockets.downstream] + */ + 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); + /** + * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no + * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter + * chain is to be dynamically updated or removed via FCDS a unique name must be provided. + */ + 'name': (string); + /** + * [#not-implemented-hide:] The configuration to specify whether the filter chain will be built on-demand. + * If this field is not empty, the filter chain will be built on-demand. + * Otherwise, the filter chain will be built normally and block listener warming. + */ + 'on_demand_configuration': (_envoy_config_listener_v3_FilterChain_OnDemandConfiguration__Output | null); + /** + * If present and nonzero, the amount of time to allow incoming connections to complete any + * transport socket negotiations. If this expires before the transport reports connection + * establishment, the connection is summarily closed. + */ + 'transport_socket_connect_timeout': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts new file mode 100644 index 000000000..259888217 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts @@ -0,0 +1,305 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto + +import type { CidrRange as _envoy_config_core_v3_CidrRange, CidrRange__Output as _envoy_config_core_v3_CidrRange__Output } from '../../../../envoy/config/core/v3/CidrRange'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto + +export enum _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType { + /** + * Any connection source matches. + */ + ANY = 0, + /** + * Match a connection originating from the same host. + */ + SAME_IP_OR_LOOPBACK = 1, + /** + * Match a connection originating from a different host. + */ + EXTERNAL = 2, +} + +/** + * Specifies the match criteria for selecting a specific filter chain for a + * listener. + * + * In order for a filter chain to be selected, *ALL* of its criteria must be + * fulfilled by the incoming connection, properties of which are set by the + * networking stack and/or listener filters. + * + * The following order applies: + * + * 1. Destination port. + * 2. Destination IP address. + * 3. Server name (e.g. SNI for TLS protocol), + * 4. Transport protocol. + * 5. Application protocols (e.g. ALPN for TLS protocol). + * 6. Directly connected source IP address (this will only be different from the source IP address + * when using a listener filter that overrides the source address, such as the :ref:`Proxy Protocol + * listener filter `). + * 7. Source type (e.g. any, local or external network). + * 8. Source IP address. + * 9. Source port. + * + * For criteria that allow ranges or wildcards, the most specific value in any + * of the configured filter chains that matches the incoming connection is going + * to be used (e.g. for SNI ``www.example.com`` the most specific match would be + * ``www.example.com``, then ``*.example.com``, then ``*.com``, then any filter + * chain without ``server_names`` requirements). + * + * A different way to reason about the filter chain matches: + * Suppose there exists N filter chains. Prune the filter chain set using the above 8 steps. + * In each step, filter chains which most specifically matches the attributes continue to the next step. + * The listener guarantees at most 1 filter chain is left after all of the steps. + * + * Example: + * + * For destination port, filter chains specifying the destination port of incoming traffic are the + * most specific match. If none of the filter chains specifies the exact destination port, the filter + * chains which do not specify ports are the most specific match. Filter chains specifying the + * wrong port can never be the most specific match. + * + * [#comment: Implemented rules are kept in the preference order, with deprecated fields + * listed at the end, because that's how we want to list them in the docs. + * + * [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] + * [#next-free-field: 14] + */ +export interface FilterChainMatch { + /** + * If non-empty, an IP address and prefix length to match addresses when the + * listener is bound to 0.0.0.0/:: or when use_original_dst is specified. + */ + 'prefix_ranges'?: (_envoy_config_core_v3_CidrRange)[]; + /** + * If non-empty, an IP address and suffix length to match addresses when the + * listener is bound to 0.0.0.0/:: or when use_original_dst is specified. + * [#not-implemented-hide:] + */ + 'address_suffix'?: (string); + /** + * [#not-implemented-hide:] + */ + 'suffix_len'?: (_google_protobuf_UInt32Value | null); + /** + * The criteria is satisfied if the source IP address of the downstream + * connection is contained in at least one of the specified subnets. If the + * parameter is not specified or the list is empty, the source IP address is + * ignored. + */ + 'source_prefix_ranges'?: (_envoy_config_core_v3_CidrRange)[]; + /** + * The criteria is satisfied if the source port of the downstream connection + * is contained in at least one of the specified ports. If the parameter is + * not specified, the source port is ignored. + */ + 'source_ports'?: (number)[]; + /** + * Optional destination port to consider when use_original_dst is set on the + * listener in determining a filter chain match. + */ + 'destination_port'?: (_google_protobuf_UInt32Value | null); + /** + * If non-empty, a transport protocol to consider when determining a filter chain match. + * This value will be compared against the transport protocol of a new connection, when + * it's detected by one of the listener filters. + * + * Suggested values include: + * + * * ``raw_buffer`` - default, used when no transport protocol is detected, + * * ``tls`` - set by :ref:`envoy.filters.listener.tls_inspector ` + * when TLS protocol is detected. + */ + 'transport_protocol'?: (string); + /** + * If non-empty, a list of application protocols (e.g. ALPN for TLS protocol) to consider when + * determining a filter chain match. Those values will be compared against the application + * protocols of a new connection, when detected by one of the listener filters. + * + * Suggested values include: + * + * * ``http/1.1`` - set by :ref:`envoy.filters.listener.tls_inspector + * `, + * * ``h2`` - set by :ref:`envoy.filters.listener.tls_inspector ` + * + * .. attention:: + * + * Currently, only :ref:`TLS Inspector ` provides + * application protocol detection based on the requested + * `ALPN `_ values. + * + * However, the use of ALPN is pretty much limited to the HTTP/2 traffic on the Internet, + * and matching on values other than ``h2`` is going to lead to a lot of false negatives, + * unless all connecting clients are known to use ALPN. + */ + 'application_protocols'?: (string)[]; + /** + * If non-empty, a list of server names (e.g. SNI for TLS protocol) to consider when determining + * a filter chain match. Those values will be compared against the server names of a new + * connection, when detected by one of the listener filters. + * + * The server name will be matched against all wildcard domains, i.e. ``www.example.com`` + * will be first matched against ``www.example.com``, then ``*.example.com``, then ``*.com``. + * + * Note that partial wildcards are not supported, and values like ``*w.example.com`` are invalid. + * + * .. attention:: + * + * See the :ref:`FAQ entry ` on how to configure SNI for more + * information. + */ + 'server_names'?: (string)[]; + /** + * Specifies the connection source IP match type. Can be any, local or external network. + */ + 'source_type'?: (_envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType | keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); + /** + * The criteria is satisfied if the directly connected source IP address of the downstream + * connection is contained in at least one of the specified subnets. If the parameter is not + * specified or the list is empty, the directly connected source IP address is ignored. + */ + 'direct_source_prefix_ranges'?: (_envoy_config_core_v3_CidrRange)[]; +} + +/** + * Specifies the match criteria for selecting a specific filter chain for a + * listener. + * + * In order for a filter chain to be selected, *ALL* of its criteria must be + * fulfilled by the incoming connection, properties of which are set by the + * networking stack and/or listener filters. + * + * The following order applies: + * + * 1. Destination port. + * 2. Destination IP address. + * 3. Server name (e.g. SNI for TLS protocol), + * 4. Transport protocol. + * 5. Application protocols (e.g. ALPN for TLS protocol). + * 6. Directly connected source IP address (this will only be different from the source IP address + * when using a listener filter that overrides the source address, such as the :ref:`Proxy Protocol + * listener filter `). + * 7. Source type (e.g. any, local or external network). + * 8. Source IP address. + * 9. Source port. + * + * For criteria that allow ranges or wildcards, the most specific value in any + * of the configured filter chains that matches the incoming connection is going + * to be used (e.g. for SNI ``www.example.com`` the most specific match would be + * ``www.example.com``, then ``*.example.com``, then ``*.com``, then any filter + * chain without ``server_names`` requirements). + * + * A different way to reason about the filter chain matches: + * Suppose there exists N filter chains. Prune the filter chain set using the above 8 steps. + * In each step, filter chains which most specifically matches the attributes continue to the next step. + * The listener guarantees at most 1 filter chain is left after all of the steps. + * + * Example: + * + * For destination port, filter chains specifying the destination port of incoming traffic are the + * most specific match. If none of the filter chains specifies the exact destination port, the filter + * chains which do not specify ports are the most specific match. Filter chains specifying the + * wrong port can never be the most specific match. + * + * [#comment: Implemented rules are kept in the preference order, with deprecated fields + * listed at the end, because that's how we want to list them in the docs. + * + * [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] + * [#next-free-field: 14] + */ +export interface FilterChainMatch__Output { + /** + * If non-empty, an IP address and prefix length to match addresses when the + * listener is bound to 0.0.0.0/:: or when use_original_dst is specified. + */ + 'prefix_ranges': (_envoy_config_core_v3_CidrRange__Output)[]; + /** + * If non-empty, an IP address and suffix length to match addresses when the + * listener is bound to 0.0.0.0/:: or when use_original_dst is specified. + * [#not-implemented-hide:] + */ + 'address_suffix': (string); + /** + * [#not-implemented-hide:] + */ + 'suffix_len': (_google_protobuf_UInt32Value__Output | null); + /** + * The criteria is satisfied if the source IP address of the downstream + * connection is contained in at least one of the specified subnets. If the + * parameter is not specified or the list is empty, the source IP address is + * ignored. + */ + 'source_prefix_ranges': (_envoy_config_core_v3_CidrRange__Output)[]; + /** + * The criteria is satisfied if the source port of the downstream connection + * is contained in at least one of the specified ports. If the parameter is + * not specified, the source port is ignored. + */ + 'source_ports': (number)[]; + /** + * Optional destination port to consider when use_original_dst is set on the + * listener in determining a filter chain match. + */ + 'destination_port': (_google_protobuf_UInt32Value__Output | null); + /** + * If non-empty, a transport protocol to consider when determining a filter chain match. + * This value will be compared against the transport protocol of a new connection, when + * it's detected by one of the listener filters. + * + * Suggested values include: + * + * * ``raw_buffer`` - default, used when no transport protocol is detected, + * * ``tls`` - set by :ref:`envoy.filters.listener.tls_inspector ` + * when TLS protocol is detected. + */ + 'transport_protocol': (string); + /** + * If non-empty, a list of application protocols (e.g. ALPN for TLS protocol) to consider when + * determining a filter chain match. Those values will be compared against the application + * protocols of a new connection, when detected by one of the listener filters. + * + * Suggested values include: + * + * * ``http/1.1`` - set by :ref:`envoy.filters.listener.tls_inspector + * `, + * * ``h2`` - set by :ref:`envoy.filters.listener.tls_inspector ` + * + * .. attention:: + * + * Currently, only :ref:`TLS Inspector ` provides + * application protocol detection based on the requested + * `ALPN `_ values. + * + * However, the use of ALPN is pretty much limited to the HTTP/2 traffic on the Internet, + * and matching on values other than ``h2`` is going to lead to a lot of false negatives, + * unless all connecting clients are known to use ALPN. + */ + 'application_protocols': (string)[]; + /** + * If non-empty, a list of server names (e.g. SNI for TLS protocol) to consider when determining + * a filter chain match. Those values will be compared against the server names of a new + * connection, when detected by one of the listener filters. + * + * The server name will be matched against all wildcard domains, i.e. ``www.example.com`` + * will be first matched against ``www.example.com``, then ``*.example.com``, then ``*.com``. + * + * Note that partial wildcards are not supported, and values like ``*w.example.com`` are invalid. + * + * .. attention:: + * + * See the :ref:`FAQ entry ` on how to configure SNI for more + * information. + */ + 'server_names': (string)[]; + /** + * Specifies the connection source IP match type. Can be any, local or external network. + */ + 'source_type': (keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); + /** + * The criteria is satisfied if the directly connected source IP address of the downstream + * connection is contained in at least one of the specified subnets. If the parameter is not + * specified or the list is empty, the directly connected source IP address is ignored. + */ + 'direct_source_prefix_ranges': (_envoy_config_core_v3_CidrRange__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts new file mode 100644 index 000000000..3df1006b7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts @@ -0,0 +1,624 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { FilterChain as _envoy_config_listener_v3_FilterChain, FilterChain__Output as _envoy_config_listener_v3_FilterChain__Output } from '../../../../envoy/config/listener/v3/FilterChain'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { ListenerFilter as _envoy_config_listener_v3_ListenerFilter, ListenerFilter__Output as _envoy_config_listener_v3_ListenerFilter__Output } from '../../../../envoy/config/listener/v3/ListenerFilter'; +import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { TrafficDirection as _envoy_config_core_v3_TrafficDirection } from '../../../../envoy/config/core/v3/TrafficDirection'; +import type { UdpListenerConfig as _envoy_config_listener_v3_UdpListenerConfig, UdpListenerConfig__Output as _envoy_config_listener_v3_UdpListenerConfig__Output } from '../../../../envoy/config/listener/v3/UdpListenerConfig'; +import type { ApiListener as _envoy_config_listener_v3_ApiListener, ApiListener__Output as _envoy_config_listener_v3_ApiListener__Output } from '../../../../envoy/config/listener/v3/ApiListener'; +import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../envoy/config/accesslog/v3/AccessLog'; + +/** + * Configuration for listener connection balancing. + */ +export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig { + /** + * If specified, the listener will use the exact connection balancer. + */ + 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance | null); + 'balance_type'?: "exact_balance"; +} + +/** + * Configuration for listener connection balancing. + */ +export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output { + /** + * If specified, the listener will use the exact connection balancer. + */ + 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance__Output | null); + 'balance_type': "exact_balance"; +} + +/** + * [#not-implemented-hide:] + */ +export interface _envoy_config_listener_v3_Listener_DeprecatedV1 { + /** + * Whether the listener should bind to the port. A listener that doesn't + * bind can only receive connections redirected from other listeners that + * set use_original_dst parameter to true. Default is true. + * + * This is deprecated. Use :ref:`Listener.bind_to_port + * ` + */ + 'bind_to_port'?: (_google_protobuf_BoolValue | null); +} + +/** + * [#not-implemented-hide:] + */ +export interface _envoy_config_listener_v3_Listener_DeprecatedV1__Output { + /** + * Whether the listener should bind to the port. A listener that doesn't + * bind can only receive connections redirected from other listeners that + * set use_original_dst parameter to true. Default is true. + * + * This is deprecated. Use :ref:`Listener.bind_to_port + * ` + */ + 'bind_to_port': (_google_protobuf_BoolValue__Output | null); +} + +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto + +export enum _envoy_config_listener_v3_Listener_DrainType { + /** + * Drain in response to calling /healthcheck/fail admin endpoint (along with the health check + * filter), listener removal/modification, and hot restart. + */ + DEFAULT = 0, + /** + * Drain in response to listener removal/modification and hot restart. This setting does not + * include /healthcheck/fail. This setting may be desirable if Envoy is hosting both ingress + * and egress listeners. + */ + MODIFY_ONLY = 1, +} + +/** + * A connection balancer implementation that does exact balancing. This means that a lock is + * held during balancing so that connection counts are nearly exactly balanced between worker + * threads. This is "nearly" exact in the sense that a connection might close in parallel thus + * making the counts incorrect, but this should be rectified on the next accept. This balancer + * sacrifices accept throughput for accuracy and should be used when there are a small number of + * connections that rarely cycle (e.g., service mesh gRPC egress). + */ +export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance { +} + +/** + * A connection balancer implementation that does exact balancing. This means that a lock is + * held during balancing so that connection counts are nearly exactly balanced between worker + * threads. This is "nearly" exact in the sense that a connection might close in parallel thus + * making the counts incorrect, but this should be rectified on the next accept. This balancer + * sacrifices accept throughput for accuracy and should be used when there are a small number of + * connections that rarely cycle (e.g., service mesh gRPC egress). + */ +export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance__Output { +} + +/** + * Configuration for envoy internal listener. All the future internal listener features should be added here. + * [#not-implemented-hide:] + */ +export interface _envoy_config_listener_v3_Listener_InternalListenerConfig { +} + +/** + * Configuration for envoy internal listener. All the future internal listener features should be added here. + * [#not-implemented-hide:] + */ +export interface _envoy_config_listener_v3_Listener_InternalListenerConfig__Output { +} + +/** + * [#next-free-field: 30] + */ +export interface Listener { + /** + * The unique name by which this listener is known. If no name is provided, + * Envoy will allocate an internal UUID for the listener. If the listener is to be dynamically + * updated or removed via :ref:`LDS ` a unique name must be provided. + */ + 'name'?: (string); + /** + * The address that the listener should listen on. In general, the address must be unique, though + * that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on + * Linux as the actual port will be allocated by the OS. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * A list of filter chains to consider for this listener. The + * :ref:`FilterChain ` with the most specific + * :ref:`FilterChainMatch ` criteria is used on a + * connection. + * + * Example using SNI for filter chain selection can be found in the + * :ref:`FAQ entry `. + */ + 'filter_chains'?: (_envoy_config_listener_v3_FilterChain)[]; + /** + * If a connection is redirected using *iptables*, the port on which the proxy + * receives it might be different from the original destination address. When this flag is set to + * true, the listener hands off redirected connections to the listener associated with the + * original destination address. If there is no listener associated with the original destination + * address, the connection is handled by the listener that receives it. Defaults to false. + */ + 'use_original_dst'?: (_google_protobuf_BoolValue | null); + /** + * Soft limit on size of the listener’s new connection read and write buffers. + * If unspecified, an implementation defined default is applied (1MiB). + */ + 'per_connection_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); + /** + * Listener metadata. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata | null); + /** + * [#not-implemented-hide:] + */ + 'deprecated_v1'?: (_envoy_config_listener_v3_Listener_DeprecatedV1 | null); + /** + * The type of draining to perform at a listener-wide level. + */ + 'drain_type'?: (_envoy_config_listener_v3_Listener_DrainType | keyof typeof _envoy_config_listener_v3_Listener_DrainType); + /** + * Listener filters have the opportunity to manipulate and augment the connection metadata that + * is used in connection filter chain matching, for example. These filters are run before any in + * :ref:`filter_chains `. Order matters as the + * filters are processed sequentially right after a socket has been accepted by the listener, and + * before a connection is created. + * UDP Listener filters can be specified when the protocol in the listener socket address in + * :ref:`protocol ` is :ref:`UDP + * `. + * UDP listeners currently support a single filter. + */ + 'listener_filters'?: (_envoy_config_listener_v3_ListenerFilter)[]; + /** + * Whether the listener should be set as a transparent socket. + * When this flag is set to true, connections can be redirected to the listener using an + * *iptables* *TPROXY* target, in which case the original source and destination addresses and + * ports are preserved on accepted connections. This flag should be used in combination with + * :ref:`an original_dst ` :ref:`listener filter + * ` to mark the connections' local addresses as + * "restored." This can be used to hand off each redirected connection to another listener + * associated with the connection's destination address. Direct connections to the socket without + * using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are + * therefore treated as if they were redirected. + * When this flag is set to false, the listener's socket is explicitly reset as non-transparent. + * Setting this flag requires Envoy to run with the *CAP_NET_ADMIN* capability. + * When this flag is not set (default), the socket is not modified, i.e. the transparent option + * is neither set nor reset. + */ + 'transparent'?: (_google_protobuf_BoolValue | null); + /** + * Whether the listener should set the *IP_FREEBIND* socket option. When this + * flag is set to true, listeners can be bound to an IP address that is not + * configured on the system running Envoy. When this flag is set to false, the + * option *IP_FREEBIND* is disabled on the socket. When this flag is not set + * (default), the socket is not modified, i.e. the option is neither enabled + * nor disabled. + */ + 'freebind'?: (_google_protobuf_BoolValue | null); + /** + * Whether the listener should accept TCP Fast Open (TFO) connections. + * When this flag is set to a value greater than 0, the option TCP_FASTOPEN is enabled on + * the socket, with a queue length of the specified size + * (see `details in RFC7413 `_). + * When this flag is set to 0, the option TCP_FASTOPEN is disabled on the socket. + * When this flag is not set (default), the socket is not modified, + * i.e. the option is neither enabled nor disabled. + * + * On Linux, the net.ipv4.tcp_fastopen kernel parameter must include flag 0x2 to enable + * TCP_FASTOPEN. + * See `ip-sysctl.txt `_. + * + * On macOS, only values of 0, 1, and unset are valid; other values may result in an error. + * To set the queue length on macOS, set the net.inet.tcp.fastopen_backlog kernel parameter. + */ + 'tcp_fast_open_queue_length'?: (_google_protobuf_UInt32Value | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; + /** + * The timeout to wait for all listener filters to complete operation. If the timeout is reached, + * the accepted socket is closed without a connection being created unless + * `continue_on_listener_filters_timeout` is set to true. Specify 0 to disable the + * timeout. If not specified, a default timeout of 15s is used. + */ + 'listener_filters_timeout'?: (_google_protobuf_Duration | null); + /** + * Specifies the intended direction of the traffic relative to the local Envoy. + * This property is required on Windows for listeners using the original destination filter, + * see :ref:`Original Destination `. + */ + 'traffic_direction'?: (_envoy_config_core_v3_TrafficDirection | keyof typeof _envoy_config_core_v3_TrafficDirection); + /** + * Whether a connection should be created when listener filters timeout. Default is false. + * + * .. attention:: + * + * Some listener filters, such as :ref:`Proxy Protocol filter + * `, should not be used with this option. It will cause + * unexpected behavior when a connection is created. + */ + 'continue_on_listener_filters_timeout'?: (boolean); + /** + * If the protocol in the listener socket address in :ref:`protocol + * ` is :ref:`UDP + * `, this field specifies UDP + * listener specific configuration. + */ + 'udp_listener_config'?: (_envoy_config_listener_v3_UdpListenerConfig | null); + /** + * Used to represent an API listener, which is used in non-proxy clients. The type of API + * exposed to the non-proxy application depends on the type of API listener. + * When this field is set, no other field except for :ref:`name` + * should be set. + * + * .. note:: + * + * Currently only one ApiListener can be installed; and it can only be done via bootstrap config, + * not LDS. + * + * [#next-major-version: In the v3 API, instead of this messy approach where the socket + * listener fields are directly in the top-level Listener message and the API listener types + * are in the ApiListener message, the socket listener messages should be in their own message, + * and the top-level Listener should essentially be a oneof that selects between the + * socket listener and the various types of API listener. That way, a given Listener message + * can structurally only contain the fields of the relevant type.] + */ + 'api_listener'?: (_envoy_config_listener_v3_ApiListener | null); + /** + * The listener's connection balancer configuration, currently only applicable to TCP listeners. + * If no configuration is specified, Envoy will not attempt to balance active connections between + * worker threads. + * + * In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 + * by setting :ref:`use_original_dst ` in X + * and :ref:`bind_to_port ` to false in Y1 and Y2, + * it is recommended to disable the balance config in listener X to avoid the cost of balancing, and + * enable the balance config in Y1 and Y2 to balance the connections among the workers. + */ + 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig | null); + /** + * Deprecated. Use `enable_reuse_port` instead. + */ + 'reuse_port'?: (boolean); + /** + * Configuration for :ref:`access logs ` + * emitted by this listener. + */ + 'access_log'?: (_envoy_config_accesslog_v3_AccessLog)[]; + /** + * The maximum length a tcp listener's pending connections queue can grow to. If no value is + * provided net.core.somaxconn will be used on Linux and 128 otherwise. + */ + 'tcp_backlog_size'?: (_google_protobuf_UInt32Value | null); + /** + * The default filter chain if none of the filter chain matches. If no default filter chain is supplied, + * the connection will be closed. The filter chain match is ignored in this field. + */ + 'default_filter_chain'?: (_envoy_config_listener_v3_FilterChain | null); + /** + * Whether the listener should bind to the port. A listener that doesn't + * bind can only receive connections redirected from other listeners that set + * :ref:`use_original_dst ` + * to true. Default is true. + */ + 'bind_to_port'?: (_google_protobuf_BoolValue | null); + /** + * Used to represent an internal listener which does not listen on OSI L4 address but can be used by the + * :ref:`envoy cluster ` to create a user space connection to. + * The internal listener acts as a tcp listener. It supports listener filters and network filter chains. + * The internal listener require :ref:`address ` has + * field `envoy_internal_address`. + * + * There are some limitations are derived from the implementation. The known limitations include + * + * * :ref:`ConnectionBalanceConfig ` is not + * allowed because both cluster connection and listener connection must be owned by the same dispatcher. + * * :ref:`tcp_backlog_size ` + * * :ref:`freebind ` + * * :ref:`transparent ` + * [#not-implemented-hide:] + */ + 'internal_listener'?: (_envoy_config_listener_v3_Listener_InternalListenerConfig | null); + /** + * Optional prefix to use on listener stats. If empty, the stats will be rooted at + * `listener.
.`. If non-empty, stats will be rooted at + * `listener..`. + */ + 'stat_prefix'?: (string); + /** + * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and + * create one socket for each worker thread. This makes inbound connections + * distribute among worker threads roughly evenly in cases where there are a high number + * of connections. When this flag is set to false, all worker threads share one socket. This field + * defaults to true. + * + * .. attention:: + * + * Although this field defaults to true, it has different behavior on different platforms. See + * the following text for more information. + * + * * On Linux, reuse_port is respected for both TCP and UDP listeners. It also works correctly + * with hot restart. + * * On macOS, reuse_port for TCP does not do what it does on Linux. Instead of load balancing, + * the last socket wins and receives all connections/packets. For TCP, reuse_port is force + * disabled and the user is warned. For UDP, it is enabled, but only one worker will receive + * packets. For QUIC/H3, SW routing will send packets to other workers. For "raw" UDP, only + * a single worker will currently receive packets. + * * On Windows, reuse_port for TCP has undefined behavior. It is force disabled and the user + * is warned similar to macOS. It is left enabled for UDP with undefined behavior currently. + */ + 'enable_reuse_port'?: (_google_protobuf_BoolValue | null); + /** + * The exclusive listener type and the corresponding config. + * TODO(lambdai): https://github.com/envoyproxy/envoy/issues/15372 + * Will create and add TcpListenerConfig. Will add UdpListenerConfig and ApiListener. + * [#not-implemented-hide:] + */ + 'listener_specifier'?: "internal_listener"; +} + +/** + * [#next-free-field: 30] + */ +export interface Listener__Output { + /** + * The unique name by which this listener is known. If no name is provided, + * Envoy will allocate an internal UUID for the listener. If the listener is to be dynamically + * updated or removed via :ref:`LDS ` a unique name must be provided. + */ + 'name': (string); + /** + * The address that the listener should listen on. In general, the address must be unique, though + * that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on + * Linux as the actual port will be allocated by the OS. + */ + 'address': (_envoy_config_core_v3_Address__Output | null); + /** + * A list of filter chains to consider for this listener. The + * :ref:`FilterChain ` with the most specific + * :ref:`FilterChainMatch ` criteria is used on a + * connection. + * + * Example using SNI for filter chain selection can be found in the + * :ref:`FAQ entry `. + */ + 'filter_chains': (_envoy_config_listener_v3_FilterChain__Output)[]; + /** + * If a connection is redirected using *iptables*, the port on which the proxy + * receives it might be different from the original destination address. When this flag is set to + * true, the listener hands off redirected connections to the listener associated with the + * original destination address. If there is no listener associated with the original destination + * address, the connection is handled by the listener that receives it. Defaults to false. + */ + 'use_original_dst': (_google_protobuf_BoolValue__Output | null); + /** + * Soft limit on size of the listener’s new connection read and write buffers. + * If unspecified, an implementation defined default is applied (1MiB). + */ + 'per_connection_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); + /** + * Listener metadata. + */ + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); + /** + * [#not-implemented-hide:] + */ + 'deprecated_v1': (_envoy_config_listener_v3_Listener_DeprecatedV1__Output | null); + /** + * The type of draining to perform at a listener-wide level. + */ + 'drain_type': (keyof typeof _envoy_config_listener_v3_Listener_DrainType); + /** + * Listener filters have the opportunity to manipulate and augment the connection metadata that + * is used in connection filter chain matching, for example. These filters are run before any in + * :ref:`filter_chains `. Order matters as the + * filters are processed sequentially right after a socket has been accepted by the listener, and + * before a connection is created. + * UDP Listener filters can be specified when the protocol in the listener socket address in + * :ref:`protocol ` is :ref:`UDP + * `. + * UDP listeners currently support a single filter. + */ + 'listener_filters': (_envoy_config_listener_v3_ListenerFilter__Output)[]; + /** + * Whether the listener should be set as a transparent socket. + * When this flag is set to true, connections can be redirected to the listener using an + * *iptables* *TPROXY* target, in which case the original source and destination addresses and + * ports are preserved on accepted connections. This flag should be used in combination with + * :ref:`an original_dst ` :ref:`listener filter + * ` to mark the connections' local addresses as + * "restored." This can be used to hand off each redirected connection to another listener + * associated with the connection's destination address. Direct connections to the socket without + * using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are + * therefore treated as if they were redirected. + * When this flag is set to false, the listener's socket is explicitly reset as non-transparent. + * Setting this flag requires Envoy to run with the *CAP_NET_ADMIN* capability. + * When this flag is not set (default), the socket is not modified, i.e. the transparent option + * is neither set nor reset. + */ + 'transparent': (_google_protobuf_BoolValue__Output | null); + /** + * Whether the listener should set the *IP_FREEBIND* socket option. When this + * flag is set to true, listeners can be bound to an IP address that is not + * configured on the system running Envoy. When this flag is set to false, the + * option *IP_FREEBIND* is disabled on the socket. When this flag is not set + * (default), the socket is not modified, i.e. the option is neither enabled + * nor disabled. + */ + 'freebind': (_google_protobuf_BoolValue__Output | null); + /** + * Whether the listener should accept TCP Fast Open (TFO) connections. + * When this flag is set to a value greater than 0, the option TCP_FASTOPEN is enabled on + * the socket, with a queue length of the specified size + * (see `details in RFC7413 `_). + * When this flag is set to 0, the option TCP_FASTOPEN is disabled on the socket. + * When this flag is not set (default), the socket is not modified, + * i.e. the option is neither enabled nor disabled. + * + * On Linux, the net.ipv4.tcp_fastopen kernel parameter must include flag 0x2 to enable + * TCP_FASTOPEN. + * See `ip-sysctl.txt `_. + * + * On macOS, only values of 0, 1, and unset are valid; other values may result in an error. + * To set the queue length on macOS, set the net.inet.tcp.fastopen_backlog kernel parameter. + */ + 'tcp_fast_open_queue_length': (_google_protobuf_UInt32Value__Output | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; + /** + * The timeout to wait for all listener filters to complete operation. If the timeout is reached, + * the accepted socket is closed without a connection being created unless + * `continue_on_listener_filters_timeout` is set to true. Specify 0 to disable the + * timeout. If not specified, a default timeout of 15s is used. + */ + 'listener_filters_timeout': (_google_protobuf_Duration__Output | null); + /** + * Specifies the intended direction of the traffic relative to the local Envoy. + * This property is required on Windows for listeners using the original destination filter, + * see :ref:`Original Destination `. + */ + 'traffic_direction': (keyof typeof _envoy_config_core_v3_TrafficDirection); + /** + * Whether a connection should be created when listener filters timeout. Default is false. + * + * .. attention:: + * + * Some listener filters, such as :ref:`Proxy Protocol filter + * `, should not be used with this option. It will cause + * unexpected behavior when a connection is created. + */ + 'continue_on_listener_filters_timeout': (boolean); + /** + * If the protocol in the listener socket address in :ref:`protocol + * ` is :ref:`UDP + * `, this field specifies UDP + * listener specific configuration. + */ + 'udp_listener_config': (_envoy_config_listener_v3_UdpListenerConfig__Output | null); + /** + * Used to represent an API listener, which is used in non-proxy clients. The type of API + * exposed to the non-proxy application depends on the type of API listener. + * When this field is set, no other field except for :ref:`name` + * should be set. + * + * .. note:: + * + * Currently only one ApiListener can be installed; and it can only be done via bootstrap config, + * not LDS. + * + * [#next-major-version: In the v3 API, instead of this messy approach where the socket + * listener fields are directly in the top-level Listener message and the API listener types + * are in the ApiListener message, the socket listener messages should be in their own message, + * and the top-level Listener should essentially be a oneof that selects between the + * socket listener and the various types of API listener. That way, a given Listener message + * can structurally only contain the fields of the relevant type.] + */ + 'api_listener': (_envoy_config_listener_v3_ApiListener__Output | null); + /** + * The listener's connection balancer configuration, currently only applicable to TCP listeners. + * If no configuration is specified, Envoy will not attempt to balance active connections between + * worker threads. + * + * In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 + * by setting :ref:`use_original_dst ` in X + * and :ref:`bind_to_port ` to false in Y1 and Y2, + * it is recommended to disable the balance config in listener X to avoid the cost of balancing, and + * enable the balance config in Y1 and Y2 to balance the connections among the workers. + */ + 'connection_balance_config': (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output | null); + /** + * Deprecated. Use `enable_reuse_port` instead. + */ + 'reuse_port': (boolean); + /** + * Configuration for :ref:`access logs ` + * emitted by this listener. + */ + 'access_log': (_envoy_config_accesslog_v3_AccessLog__Output)[]; + /** + * The maximum length a tcp listener's pending connections queue can grow to. If no value is + * provided net.core.somaxconn will be used on Linux and 128 otherwise. + */ + 'tcp_backlog_size': (_google_protobuf_UInt32Value__Output | null); + /** + * The default filter chain if none of the filter chain matches. If no default filter chain is supplied, + * the connection will be closed. The filter chain match is ignored in this field. + */ + 'default_filter_chain': (_envoy_config_listener_v3_FilterChain__Output | null); + /** + * Whether the listener should bind to the port. A listener that doesn't + * bind can only receive connections redirected from other listeners that set + * :ref:`use_original_dst ` + * to true. Default is true. + */ + 'bind_to_port': (_google_protobuf_BoolValue__Output | null); + /** + * Used to represent an internal listener which does not listen on OSI L4 address but can be used by the + * :ref:`envoy cluster ` to create a user space connection to. + * The internal listener acts as a tcp listener. It supports listener filters and network filter chains. + * The internal listener require :ref:`address ` has + * field `envoy_internal_address`. + * + * There are some limitations are derived from the implementation. The known limitations include + * + * * :ref:`ConnectionBalanceConfig ` is not + * allowed because both cluster connection and listener connection must be owned by the same dispatcher. + * * :ref:`tcp_backlog_size ` + * * :ref:`freebind ` + * * :ref:`transparent ` + * [#not-implemented-hide:] + */ + 'internal_listener'?: (_envoy_config_listener_v3_Listener_InternalListenerConfig__Output | null); + /** + * Optional prefix to use on listener stats. If empty, the stats will be rooted at + * `listener.
.`. If non-empty, stats will be rooted at + * `listener..`. + */ + 'stat_prefix': (string); + /** + * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and + * create one socket for each worker thread. This makes inbound connections + * distribute among worker threads roughly evenly in cases where there are a high number + * of connections. When this flag is set to false, all worker threads share one socket. This field + * defaults to true. + * + * .. attention:: + * + * Although this field defaults to true, it has different behavior on different platforms. See + * the following text for more information. + * + * * On Linux, reuse_port is respected for both TCP and UDP listeners. It also works correctly + * with hot restart. + * * On macOS, reuse_port for TCP does not do what it does on Linux. Instead of load balancing, + * the last socket wins and receives all connections/packets. For TCP, reuse_port is force + * disabled and the user is warned. For UDP, it is enabled, but only one worker will receive + * packets. For QUIC/H3, SW routing will send packets to other workers. For "raw" UDP, only + * a single worker will currently receive packets. + * * On Windows, reuse_port for TCP has undefined behavior. It is force disabled and the user + * is warned similar to macOS. It is left enabled for UDP with undefined behavior currently. + */ + 'enable_reuse_port': (_google_protobuf_BoolValue__Output | null); + /** + * The exclusive listener type and the corresponding config. + * TODO(lambdai): https://github.com/envoyproxy/envoy/issues/15372 + * Will create and add TcpListenerConfig. Will add UdpListenerConfig and ApiListener. + * [#not-implemented-hide:] + */ + 'listener_specifier': "internal_listener"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts new file mode 100644 index 000000000..590ca8ed3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts @@ -0,0 +1,19 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto + +import type { CollectionEntry as _xds_core_v3_CollectionEntry, CollectionEntry__Output as _xds_core_v3_CollectionEntry__Output } from '../../../../xds/core/v3/CollectionEntry'; + +/** + * Listener list collections. Entries are *Listener* resources or references. + * [#not-implemented-hide:] + */ +export interface ListenerCollection { + 'entries'?: (_xds_core_v3_CollectionEntry)[]; +} + +/** + * Listener list collections. Entries are *Listener* resources or references. + * [#not-implemented-hide:] + */ +export interface ListenerCollection__Output { + 'entries': (_xds_core_v3_CollectionEntry__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts new file mode 100644 index 000000000..be7242849 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts @@ -0,0 +1,46 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { ListenerFilterChainMatchPredicate as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate, ListenerFilterChainMatchPredicate__Output as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output } from '../../../../envoy/config/listener/v3/ListenerFilterChainMatchPredicate'; + +export interface ListenerFilter { + /** + * The name of the filter to instantiate. The name must match a + * :ref:`supported filter `. + */ + 'name'?: (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + * [#extension-category: envoy.filters.listener,envoy.filters.udp_listener] + */ + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Optional match predicate used to disable the filter. The filter is enabled when this field is empty. + * See :ref:`ListenerFilterChainMatchPredicate ` + * for further examples. + */ + 'filter_disabled'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate | null); + 'config_type'?: "typed_config"; +} + +export interface ListenerFilter__Output { + /** + * The name of the filter to instantiate. The name must match a + * :ref:`supported filter `. + */ + 'name': (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + * [#extension-category: envoy.filters.listener,envoy.filters.udp_listener] + */ + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Optional match predicate used to disable the filter. The filter is enabled when this field is empty. + * See :ref:`ListenerFilterChainMatchPredicate ` + * for further examples. + */ + 'filter_disabled': (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output | null); + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts new file mode 100644 index 000000000..bb743a29d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts @@ -0,0 +1,136 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto + +import type { ListenerFilterChainMatchPredicate as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate, ListenerFilterChainMatchPredicate__Output as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output } from '../../../../envoy/config/listener/v3/ListenerFilterChainMatchPredicate'; +import type { Int32Range as _envoy_type_v3_Int32Range, Int32Range__Output as _envoy_type_v3_Int32Range__Output } from '../../../../envoy/type/v3/Int32Range'; + +/** + * A set of match configurations used for logical operations. + */ +export interface _envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet { + /** + * The list of rules that make up the set. + */ + 'rules'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate)[]; +} + +/** + * A set of match configurations used for logical operations. + */ +export interface _envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output { + /** + * The list of rules that make up the set. + */ + 'rules': (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output)[]; +} + +/** + * Listener filter chain match configuration. This is a recursive structure which allows complex + * nested match configurations to be built using various logical operators. + * + * Examples: + * + * * Matches if the destination port is 3306. + * + * .. code-block:: yaml + * + * destination_port_range: + * start: 3306 + * end: 3307 + * + * * Matches if the destination port is 3306 or 15000. + * + * .. code-block:: yaml + * + * or_match: + * rules: + * - destination_port_range: + * start: 3306 + * end: 3307 + * - destination_port_range: + * start: 15000 + * end: 15001 + * + * [#next-free-field: 6] + */ +export interface ListenerFilterChainMatchPredicate { + /** + * A set that describes a logical OR. If any member of the set matches, the match configuration + * matches. + */ + 'or_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet | null); + /** + * A set that describes a logical AND. If all members of the set match, the match configuration + * matches. + */ + 'and_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet | null); + /** + * A negation match. The match configuration will match if the negated match condition matches. + */ + 'not_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate | null); + /** + * The match configuration will always match. + */ + 'any_match'?: (boolean); + /** + * Match destination port. Particularly, the match evaluation must use the recovered local port if + * the owning listener filter is after :ref:`an original_dst listener filter `. + */ + 'destination_port_range'?: (_envoy_type_v3_Int32Range | null); + 'rule'?: "or_match"|"and_match"|"not_match"|"any_match"|"destination_port_range"; +} + +/** + * Listener filter chain match configuration. This is a recursive structure which allows complex + * nested match configurations to be built using various logical operators. + * + * Examples: + * + * * Matches if the destination port is 3306. + * + * .. code-block:: yaml + * + * destination_port_range: + * start: 3306 + * end: 3307 + * + * * Matches if the destination port is 3306 or 15000. + * + * .. code-block:: yaml + * + * or_match: + * rules: + * - destination_port_range: + * start: 3306 + * end: 3307 + * - destination_port_range: + * start: 15000 + * end: 15001 + * + * [#next-free-field: 6] + */ +export interface ListenerFilterChainMatchPredicate__Output { + /** + * A set that describes a logical OR. If any member of the set matches, the match configuration + * matches. + */ + 'or_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output | null); + /** + * A set that describes a logical AND. If all members of the set match, the match configuration + * matches. + */ + 'and_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output | null); + /** + * A negation match. The match configuration will match if the negated match condition matches. + */ + 'not_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output | null); + /** + * The match configuration will always match. + */ + 'any_match'?: (boolean); + /** + * Match destination port. Particularly, the match evaluation must use the recovered local port if + * the owning listener filter is after :ref:`an original_dst listener filter `. + */ + 'destination_port_range'?: (_envoy_type_v3_Int32Range__Output | null); + 'rule': "or_match"|"and_match"|"not_match"|"any_match"|"destination_port_range"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts new file mode 100644 index 000000000..5e01a772f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts @@ -0,0 +1,97 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/quic_config.proto + +import type { QuicProtocolOptions as _envoy_config_core_v3_QuicProtocolOptions, QuicProtocolOptions__Output as _envoy_config_core_v3_QuicProtocolOptions__Output } from '../../../../envoy/config/core/v3/QuicProtocolOptions'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { RuntimeFeatureFlag as _envoy_config_core_v3_RuntimeFeatureFlag, RuntimeFeatureFlag__Output as _envoy_config_core_v3_RuntimeFeatureFlag__Output } from '../../../../envoy/config/core/v3/RuntimeFeatureFlag'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Configuration specific to the UDP QUIC listener. + * [#next-free-field: 8] + */ +export interface QuicProtocolOptions { + 'quic_protocol_options'?: (_envoy_config_core_v3_QuicProtocolOptions | null); + /** + * Maximum number of milliseconds that connection will be alive when there is + * no network activity. 300000ms if not specified. + */ + 'idle_timeout'?: (_google_protobuf_Duration | null); + /** + * Connection timeout in milliseconds before the crypto handshake is finished. + * 20000ms if not specified. + */ + 'crypto_handshake_timeout'?: (_google_protobuf_Duration | null); + /** + * Runtime flag that controls whether the listener is enabled or not. If not specified, defaults + * to enabled. + */ + 'enabled'?: (_envoy_config_core_v3_RuntimeFeatureFlag | null); + /** + * A multiplier to number of connections which is used to determine how many packets to read per + * event loop. A reasonable number should allow the listener to process enough payload but not + * starve TCP and other UDP sockets and also prevent long event loop duration. + * The default value is 32. This means if there are N QUIC connections, the total number of + * packets to read in each read event will be 32 * N. + * The actual number of packets to read in total by the UDP listener is also + * bound by 6000, regardless of this field or how many connections there are. + */ + 'packets_to_read_to_connection_count_ratio'?: (_google_protobuf_UInt32Value | null); + /** + * Configure which implementation of `quic::QuicCryptoClientStreamBase` to be used for this listener. + * If not specified the :ref:`QUICHE default one configured by ` will be used. + * [#extension-category: envoy.quic.server.crypto_stream] + */ + 'crypto_stream_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Configure which implementation of `quic::ProofSource` to be used for this listener. + * If not specified the :ref:`default one configured by ` will be used. + * [#extension-category: envoy.quic.proof_source] + */ + 'proof_source_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +/** + * Configuration specific to the UDP QUIC listener. + * [#next-free-field: 8] + */ +export interface QuicProtocolOptions__Output { + 'quic_protocol_options': (_envoy_config_core_v3_QuicProtocolOptions__Output | null); + /** + * Maximum number of milliseconds that connection will be alive when there is + * no network activity. 300000ms if not specified. + */ + 'idle_timeout': (_google_protobuf_Duration__Output | null); + /** + * Connection timeout in milliseconds before the crypto handshake is finished. + * 20000ms if not specified. + */ + 'crypto_handshake_timeout': (_google_protobuf_Duration__Output | null); + /** + * Runtime flag that controls whether the listener is enabled or not. If not specified, defaults + * to enabled. + */ + 'enabled': (_envoy_config_core_v3_RuntimeFeatureFlag__Output | null); + /** + * A multiplier to number of connections which is used to determine how many packets to read per + * event loop. A reasonable number should allow the listener to process enough payload but not + * starve TCP and other UDP sockets and also prevent long event loop duration. + * The default value is 32. This means if there are N QUIC connections, the total number of + * packets to read in each read event will be 32 * N. + * The actual number of packets to read in total by the UDP listener is also + * bound by 6000, regardless of this field or how many connections there are. + */ + 'packets_to_read_to_connection_count_ratio': (_google_protobuf_UInt32Value__Output | null); + /** + * Configure which implementation of `quic::QuicCryptoClientStreamBase` to be used for this listener. + * If not specified the :ref:`QUICHE default one configured by ` will be used. + * [#extension-category: envoy.quic.server.crypto_stream] + */ + 'crypto_stream_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Configure which implementation of `quic::ProofSource` to be used for this listener. + * If not specified the :ref:`default one configured by ` will be used. + * [#extension-category: envoy.quic.proof_source] + */ + 'proof_source_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts new file mode 100644 index 000000000..f4c220e20 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts @@ -0,0 +1,48 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/udp_listener_config.proto + +import type { UdpSocketConfig as _envoy_config_core_v3_UdpSocketConfig, UdpSocketConfig__Output as _envoy_config_core_v3_UdpSocketConfig__Output } from '../../../../envoy/config/core/v3/UdpSocketConfig'; +import type { QuicProtocolOptions as _envoy_config_listener_v3_QuicProtocolOptions, QuicProtocolOptions__Output as _envoy_config_listener_v3_QuicProtocolOptions__Output } from '../../../../envoy/config/listener/v3/QuicProtocolOptions'; + +/** + * [#next-free-field: 8] + */ +export interface UdpListenerConfig { + /** + * UDP socket configuration for the listener. The default for + * :ref:`prefer_gro ` is false for + * listener sockets. If receiving a large amount of datagrams from a small number of sources, it + * may be worthwhile to enable this option after performance testing. + */ + 'downstream_socket_config'?: (_envoy_config_core_v3_UdpSocketConfig | null); + /** + * Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set + * to the default object to enable QUIC without modifying any additional options. + * + * .. warning:: + * QUIC support is currently alpha and should be used with caution. Please + * see :ref:`here ` for details. + */ + 'quic_options'?: (_envoy_config_listener_v3_QuicProtocolOptions | null); +} + +/** + * [#next-free-field: 8] + */ +export interface UdpListenerConfig__Output { + /** + * UDP socket configuration for the listener. The default for + * :ref:`prefer_gro ` is false for + * listener sockets. If receiving a large amount of datagrams from a small number of sources, it + * may be worthwhile to enable this option after performance testing. + */ + 'downstream_socket_config': (_envoy_config_core_v3_UdpSocketConfig__Output | null); + /** + * Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set + * to the default object to enable QUIC without modifying any additional options. + * + * .. warning:: + * QUIC support is currently alpha and should be used with caution. Please + * see :ref:`here ` for details. + */ + 'quic_options': (_envoy_config_listener_v3_QuicProtocolOptions__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts new file mode 100644 index 000000000..4cf705f10 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts @@ -0,0 +1,65 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. + * The sink emits stats with `DogStatsD `_ + * compatible tags. Tags are configurable via :ref:`StatsConfig + * `. + * [#extension: envoy.stat_sinks.dog_statsd] + */ +export interface DogStatsdSink { + /** + * The UDP address of a running DogStatsD compliant listener. If specified, + * statistics will be flushed to this address. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * Optional custom metric name prefix. See :ref:`StatsdSink's prefix field + * ` for more details. + */ + 'prefix'?: (string); + /** + * Optional max datagram size to use when sending UDP messages. By default Envoy + * will emit one metric per datagram. By specifying a max-size larger than a single + * metric, Envoy will emit multiple, new-line separated metrics. The max datagram + * size should not exceed your network's MTU. + * + * Note that this value may not be respected if smaller than a single metric. + */ + 'max_bytes_per_datagram'?: (_google_protobuf_UInt64Value | null); + 'dog_statsd_specifier'?: "address"; +} + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. + * The sink emits stats with `DogStatsD `_ + * compatible tags. Tags are configurable via :ref:`StatsConfig + * `. + * [#extension: envoy.stat_sinks.dog_statsd] + */ +export interface DogStatsdSink__Output { + /** + * The UDP address of a running DogStatsD compliant listener. If specified, + * statistics will be flushed to this address. + */ + 'address'?: (_envoy_config_core_v3_Address__Output | null); + /** + * Optional custom metric name prefix. See :ref:`StatsdSink's prefix field + * ` for more details. + */ + 'prefix': (string); + /** + * Optional max datagram size to use when sending UDP messages. By default Envoy + * will emit one metric per datagram. By specifying a max-size larger than a single + * metric, Envoy will emit multiple, new-line separated metrics. The max datagram + * size should not exceed your network's MTU. + * + * Note that this value may not be respected if smaller than a single metric. + */ + 'max_bytes_per_datagram': (_google_protobuf_UInt64Value__Output | null); + 'dog_statsd_specifier': "address"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts new file mode 100644 index 000000000..036958a49 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; + +/** + * Specifies a matcher for stats and the buckets that matching stats should use. + */ +export interface HistogramBucketSettings { + /** + * The stats that this rule applies to. The match is applied to the original stat name + * before tag-extraction, for example `cluster.exampleclustername.upstream_cx_length_ms`. + */ + 'match'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * Each value is the upper bound of a bucket. Each bucket must be greater than 0 and unique. + * The order of the buckets does not matter. + */ + 'buckets'?: (number | string)[]; +} + +/** + * Specifies a matcher for stats and the buckets that matching stats should use. + */ +export interface HistogramBucketSettings__Output { + /** + * The stats that this rule applies to. The match is applied to the original stat name + * before tag-extraction, for example `cluster.exampleclustername.upstream_cx_length_ms`. + */ + 'match': (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * Each value is the upper bound of a bucket. Each bucket must be greater than 0 and unique. + * The order of the buckets does not matter. + */ + 'buckets': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts new file mode 100644 index 000000000..b8fb2ed8e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts @@ -0,0 +1,61 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.hystrix* sink. + * The sink emits stats in `text/event-stream + * `_ + * formatted stream for use by `Hystrix dashboard + * `_. + * + * Note that only a single HystrixSink should be configured. + * + * Streaming is started through an admin endpoint :http:get:`/hystrix_event_stream`. + * [#extension: envoy.stat_sinks.hystrix] + */ +export interface HystrixSink { + /** + * The number of buckets the rolling statistical window is divided into. + * + * Each time the sink is flushed, all relevant Envoy statistics are sampled and + * added to the rolling window (removing the oldest samples in the window + * in the process). The sink then outputs the aggregate statistics across the + * current rolling window to the event stream(s). + * + * rolling_window(ms) = stats_flush_interval(ms) * num_of_buckets + * + * More detailed explanation can be found in `Hystrix wiki + * `_. + */ + 'num_buckets'?: (number | string | Long); +} + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.hystrix* sink. + * The sink emits stats in `text/event-stream + * `_ + * formatted stream for use by `Hystrix dashboard + * `_. + * + * Note that only a single HystrixSink should be configured. + * + * Streaming is started through an admin endpoint :http:get:`/hystrix_event_stream`. + * [#extension: envoy.stat_sinks.hystrix] + */ +export interface HystrixSink__Output { + /** + * The number of buckets the rolling statistical window is divided into. + * + * Each time the sink is flushed, all relevant Envoy statistics are sampled and + * added to the rolling window (removing the oldest samples in the window + * in the process). The sink then outputs the aggregate statistics across the + * current rolling window to the event stream(s). + * + * rolling_window(ms) = stats_flush_interval(ms) * num_of_buckets + * + * More detailed explanation can be found in `Hystrix wiki + * `_. + */ + 'num_buckets': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts new file mode 100644 index 000000000..df5d7c7e4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts @@ -0,0 +1,148 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { TagSpecifier as _envoy_config_metrics_v3_TagSpecifier, TagSpecifier__Output as _envoy_config_metrics_v3_TagSpecifier__Output } from '../../../../envoy/config/metrics/v3/TagSpecifier'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { StatsMatcher as _envoy_config_metrics_v3_StatsMatcher, StatsMatcher__Output as _envoy_config_metrics_v3_StatsMatcher__Output } from '../../../../envoy/config/metrics/v3/StatsMatcher'; +import type { HistogramBucketSettings as _envoy_config_metrics_v3_HistogramBucketSettings, HistogramBucketSettings__Output as _envoy_config_metrics_v3_HistogramBucketSettings__Output } from '../../../../envoy/config/metrics/v3/HistogramBucketSettings'; + +/** + * Statistics configuration such as tagging. + */ +export interface StatsConfig { + /** + * Each stat name is iteratively processed through these tag specifiers. + * When a tag is matched, the first capture group is removed from the name so + * later :ref:`TagSpecifiers ` cannot match that + * same portion of the match. + */ + 'stats_tags'?: (_envoy_config_metrics_v3_TagSpecifier)[]; + /** + * Use all default tag regexes specified in Envoy. These can be combined with + * custom tags specified in :ref:`stats_tags + * `. They will be processed before + * the custom tags. + * + * .. note:: + * + * If any default tags are specified twice, the config will be considered + * invalid. + * + * See :repo:`well_known_names.h ` for a list of the + * default tags in Envoy. + * + * If not provided, the value is assumed to be true. + */ + 'use_all_default_tags'?: (_google_protobuf_BoolValue | null); + /** + * Inclusion/exclusion matcher for stat name creation. If not provided, all stats are instantiated + * as normal. Preventing the instantiation of certain families of stats can improve memory + * performance for Envoys running especially large configs. + * + * .. warning:: + * Excluding stats may affect Envoy's behavior in undocumented ways. See + * `issue #8771 `_ for more information. + * If any unexpected behavior changes are observed, please open a new issue immediately. + */ + 'stats_matcher'?: (_envoy_config_metrics_v3_StatsMatcher | null); + /** + * Defines rules for setting the histogram buckets. Rules are evaluated in order, and the first + * match is applied. If no match is found (or if no rules are set), the following default buckets + * are used: + * + * .. code-block:: json + * + * [ + * 0.5, + * 1, + * 5, + * 10, + * 25, + * 50, + * 100, + * 250, + * 500, + * 1000, + * 2500, + * 5000, + * 10000, + * 30000, + * 60000, + * 300000, + * 600000, + * 1800000, + * 3600000 + * ] + */ + 'histogram_bucket_settings'?: (_envoy_config_metrics_v3_HistogramBucketSettings)[]; +} + +/** + * Statistics configuration such as tagging. + */ +export interface StatsConfig__Output { + /** + * Each stat name is iteratively processed through these tag specifiers. + * When a tag is matched, the first capture group is removed from the name so + * later :ref:`TagSpecifiers ` cannot match that + * same portion of the match. + */ + 'stats_tags': (_envoy_config_metrics_v3_TagSpecifier__Output)[]; + /** + * Use all default tag regexes specified in Envoy. These can be combined with + * custom tags specified in :ref:`stats_tags + * `. They will be processed before + * the custom tags. + * + * .. note:: + * + * If any default tags are specified twice, the config will be considered + * invalid. + * + * See :repo:`well_known_names.h ` for a list of the + * default tags in Envoy. + * + * If not provided, the value is assumed to be true. + */ + 'use_all_default_tags': (_google_protobuf_BoolValue__Output | null); + /** + * Inclusion/exclusion matcher for stat name creation. If not provided, all stats are instantiated + * as normal. Preventing the instantiation of certain families of stats can improve memory + * performance for Envoys running especially large configs. + * + * .. warning:: + * Excluding stats may affect Envoy's behavior in undocumented ways. See + * `issue #8771 `_ for more information. + * If any unexpected behavior changes are observed, please open a new issue immediately. + */ + 'stats_matcher': (_envoy_config_metrics_v3_StatsMatcher__Output | null); + /** + * Defines rules for setting the histogram buckets. Rules are evaluated in order, and the first + * match is applied. If no match is found (or if no rules are set), the following default buckets + * are used: + * + * .. code-block:: json + * + * [ + * 0.5, + * 1, + * 5, + * 10, + * 25, + * 50, + * 100, + * 250, + * 500, + * 1000, + * 2500, + * 5000, + * 10000, + * 30000, + * 60000, + * 300000, + * 600000, + * 1800000, + * 3600000 + * ] + */ + 'histogram_bucket_settings': (_envoy_config_metrics_v3_HistogramBucketSettings__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts new file mode 100644 index 000000000..9df9e9529 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts @@ -0,0 +1,47 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { ListStringMatcher as _envoy_type_matcher_v3_ListStringMatcher, ListStringMatcher__Output as _envoy_type_matcher_v3_ListStringMatcher__Output } from '../../../../envoy/type/matcher/v3/ListStringMatcher'; + +/** + * Configuration for disabling stat instantiation. + */ +export interface StatsMatcher { + /** + * If `reject_all` is true, then all stats are disabled. If `reject_all` is false, then all + * stats are enabled. + */ + 'reject_all'?: (boolean); + /** + * Exclusive match. All stats are enabled except for those matching one of the supplied + * StringMatcher protos. + */ + 'exclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher | null); + /** + * Inclusive match. No stats are enabled except for those matching one of the supplied + * StringMatcher protos. + */ + 'inclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher | null); + 'stats_matcher'?: "reject_all"|"exclusion_list"|"inclusion_list"; +} + +/** + * Configuration for disabling stat instantiation. + */ +export interface StatsMatcher__Output { + /** + * If `reject_all` is true, then all stats are disabled. If `reject_all` is false, then all + * stats are enabled. + */ + 'reject_all'?: (boolean); + /** + * Exclusive match. All stats are enabled except for those matching one of the supplied + * StringMatcher protos. + */ + 'exclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher__Output | null); + /** + * Inclusive match. No stats are enabled except for those matching one of the supplied + * StringMatcher protos. + */ + 'inclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher__Output | null); + 'stats_matcher': "reject_all"|"exclusion_list"|"inclusion_list"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts new file mode 100644 index 000000000..3eb8926fa --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Configuration for pluggable stats sinks. + */ +export interface StatsSink { + /** + * The name of the stats sink to instantiate. The name must match a supported + * stats sink. + * See the :ref:`extensions listed in typed_config below ` for the default list of available stats sink. + * Sinks optionally support tagged/multiple dimensional metrics. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Stats sink specific configuration which depends on the sink being instantiated. See + * :ref:`StatsdSink ` for an example. + * [#extension-category: envoy.stats_sinks] + */ + 'config_type'?: "typed_config"; +} + +/** + * Configuration for pluggable stats sinks. + */ +export interface StatsSink__Output { + /** + * The name of the stats sink to instantiate. The name must match a supported + * stats sink. + * See the :ref:`extensions listed in typed_config below ` for the default list of available stats sink. + * Sinks optionally support tagged/multiple dimensional metrics. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Stats sink specific configuration which depends on the sink being instantiated. See + * :ref:`StatsdSink ` for an example. + * [#extension-category: envoy.stats_sinks] + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts new file mode 100644 index 000000000..69d978920 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts @@ -0,0 +1,103 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.statsd* sink. This sink does not support + * tagged metrics. + * [#extension: envoy.stat_sinks.statsd] + */ +export interface StatsdSink { + /** + * The UDP address of a running `statsd `_ + * compliant listener. If specified, statistics will be flushed to this + * address. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * The name of a cluster that is running a TCP `statsd + * `_ compliant listener. If specified, + * Envoy will connect to this cluster to flush statistics. + */ + 'tcp_cluster_name'?: (string); + /** + * Optional custom prefix for StatsdSink. If + * specified, this will override the default prefix. + * For example: + * + * .. code-block:: json + * + * { + * "prefix" : "envoy-prod" + * } + * + * will change emitted stats to + * + * .. code-block:: cpp + * + * envoy-prod.test_counter:1|c + * envoy-prod.test_timer:5|ms + * + * Note that the default prefix, "envoy", will be used if a prefix is not + * specified. + * + * Stats with default prefix: + * + * .. code-block:: cpp + * + * envoy.test_counter:1|c + * envoy.test_timer:5|ms + */ + 'prefix'?: (string); + 'statsd_specifier'?: "address"|"tcp_cluster_name"; +} + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.statsd* sink. This sink does not support + * tagged metrics. + * [#extension: envoy.stat_sinks.statsd] + */ +export interface StatsdSink__Output { + /** + * The UDP address of a running `statsd `_ + * compliant listener. If specified, statistics will be flushed to this + * address. + */ + 'address'?: (_envoy_config_core_v3_Address__Output | null); + /** + * The name of a cluster that is running a TCP `statsd + * `_ compliant listener. If specified, + * Envoy will connect to this cluster to flush statistics. + */ + 'tcp_cluster_name'?: (string); + /** + * Optional custom prefix for StatsdSink. If + * specified, this will override the default prefix. + * For example: + * + * .. code-block:: json + * + * { + * "prefix" : "envoy-prod" + * } + * + * will change emitted stats to + * + * .. code-block:: cpp + * + * envoy-prod.test_counter:1|c + * envoy-prod.test_timer:5|ms + * + * Note that the default prefix, "envoy", will be used if a prefix is not + * specified. + * + * Stats with default prefix: + * + * .. code-block:: cpp + * + * envoy.test_counter:1|c + * envoy.test_timer:5|ms + */ + 'prefix': (string); + 'statsd_specifier': "address"|"tcp_cluster_name"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts new file mode 100644 index 000000000..9b0bb2467 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts @@ -0,0 +1,174 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + + +/** + * Designates a tag name and value pair. The value may be either a fixed value + * or a regex providing the value via capture groups. The specified tag will be + * unconditionally set if a fixed value, otherwise it will only be set if one + * or more capture groups in the regex match. + */ +export interface TagSpecifier { + /** + * Attaches an identifier to the tag values to identify the tag being in the + * sink. Envoy has a set of default names and regexes to extract dynamic + * portions of existing stats, which can be found in :repo:`well_known_names.h + * ` in the Envoy repository. If a :ref:`tag_name + * ` is provided in the config and + * neither :ref:`regex ` or + * :ref:`fixed_value ` were specified, + * Envoy will attempt to find that name in its set of defaults and use the accompanying regex. + * + * .. note:: + * + * It is invalid to specify the same tag name twice in a config. + */ + 'tag_name'?: (string); + /** + * Designates a tag to strip from the tag extracted name and provide as a named + * tag value for all statistics. This will only occur if any part of the name + * matches the regex provided with one or more capture groups. + * + * The first capture group identifies the portion of the name to remove. The + * second capture group (which will normally be nested inside the first) will + * designate the value of the tag for the statistic. If no second capture + * group is provided, the first will also be used to set the value of the tag. + * All other capture groups will be ignored. + * + * Example 1. a stat name ``cluster.foo_cluster.upstream_rq_timeout`` and + * one tag specifier: + * + * .. code-block:: json + * + * { + * "tag_name": "envoy.cluster_name", + * "regex": "^cluster\\.((.+?)\\.)" + * } + * + * Note that the regex will remove ``foo_cluster.`` making the tag extracted + * name ``cluster.upstream_rq_timeout`` and the tag value for + * ``envoy.cluster_name`` will be ``foo_cluster`` (note: there will be no + * ``.`` character because of the second capture group). + * + * Example 2. a stat name + * ``http.connection_manager_1.user_agent.ios.downstream_cx_total`` and two + * tag specifiers: + * + * .. code-block:: json + * + * [ + * { + * "tag_name": "envoy.http_user_agent", + * "regex": "^http(?=\\.).*?\\.user_agent\\.((.+?)\\.)\\w+?$" + * }, + * { + * "tag_name": "envoy.http_conn_manager_prefix", + * "regex": "^http\\.((.*?)\\.)" + * } + * ] + * + * The two regexes of the specifiers will be processed in the definition order. + * + * The first regex will remove ``ios.``, leaving the tag extracted name + * ``http.connection_manager_1.user_agent.downstream_cx_total``. The tag + * ``envoy.http_user_agent`` will be added with tag value ``ios``. + * + * The second regex will remove ``connection_manager_1.`` from the tag + * extracted name produced by the first regex + * ``http.connection_manager_1.user_agent.downstream_cx_total``, leaving + * ``http.user_agent.downstream_cx_total`` as the tag extracted name. The tag + * ``envoy.http_conn_manager_prefix`` will be added with the tag value + * ``connection_manager_1``. + */ + 'regex'?: (string); + /** + * Specifies a fixed tag value for the ``tag_name``. + */ + 'fixed_value'?: (string); + 'tag_value'?: "regex"|"fixed_value"; +} + +/** + * Designates a tag name and value pair. The value may be either a fixed value + * or a regex providing the value via capture groups. The specified tag will be + * unconditionally set if a fixed value, otherwise it will only be set if one + * or more capture groups in the regex match. + */ +export interface TagSpecifier__Output { + /** + * Attaches an identifier to the tag values to identify the tag being in the + * sink. Envoy has a set of default names and regexes to extract dynamic + * portions of existing stats, which can be found in :repo:`well_known_names.h + * ` in the Envoy repository. If a :ref:`tag_name + * ` is provided in the config and + * neither :ref:`regex ` or + * :ref:`fixed_value ` were specified, + * Envoy will attempt to find that name in its set of defaults and use the accompanying regex. + * + * .. note:: + * + * It is invalid to specify the same tag name twice in a config. + */ + 'tag_name': (string); + /** + * Designates a tag to strip from the tag extracted name and provide as a named + * tag value for all statistics. This will only occur if any part of the name + * matches the regex provided with one or more capture groups. + * + * The first capture group identifies the portion of the name to remove. The + * second capture group (which will normally be nested inside the first) will + * designate the value of the tag for the statistic. If no second capture + * group is provided, the first will also be used to set the value of the tag. + * All other capture groups will be ignored. + * + * Example 1. a stat name ``cluster.foo_cluster.upstream_rq_timeout`` and + * one tag specifier: + * + * .. code-block:: json + * + * { + * "tag_name": "envoy.cluster_name", + * "regex": "^cluster\\.((.+?)\\.)" + * } + * + * Note that the regex will remove ``foo_cluster.`` making the tag extracted + * name ``cluster.upstream_rq_timeout`` and the tag value for + * ``envoy.cluster_name`` will be ``foo_cluster`` (note: there will be no + * ``.`` character because of the second capture group). + * + * Example 2. a stat name + * ``http.connection_manager_1.user_agent.ios.downstream_cx_total`` and two + * tag specifiers: + * + * .. code-block:: json + * + * [ + * { + * "tag_name": "envoy.http_user_agent", + * "regex": "^http(?=\\.).*?\\.user_agent\\.((.+?)\\.)\\w+?$" + * }, + * { + * "tag_name": "envoy.http_conn_manager_prefix", + * "regex": "^http\\.((.*?)\\.)" + * } + * ] + * + * The two regexes of the specifiers will be processed in the definition order. + * + * The first regex will remove ``ios.``, leaving the tag extracted name + * ``http.connection_manager_1.user_agent.downstream_cx_total``. The tag + * ``envoy.http_user_agent`` will be added with tag value ``ios``. + * + * The second regex will remove ``connection_manager_1.`` from the tag + * extracted name produced by the first regex + * ``http.connection_manager_1.user_agent.downstream_cx_total``, leaving + * ``http.user_agent.downstream_cx_total`` as the tag extracted name. The tag + * ``envoy.http_conn_manager_prefix`` will be added with the tag value + * ``connection_manager_1``. + */ + 'regex'?: (string); + /** + * Specifies a fixed tag value for the ``tag_name``. + */ + 'fixed_value'?: (string); + 'tag_value': "regex"|"fixed_value"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts new file mode 100644 index 000000000..b3fbe1459 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts @@ -0,0 +1,50 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + + +/** + * Configuration for which accounts the WatermarkBuffer Factories should + * track. + */ +export interface BufferFactoryConfig { + /** + * The minimum power of two at which Envoy starts tracking an account. + * + * Envoy has 8 power of two buckets starting with the provided exponent below. + * Concretely the 1st bucket contains accounts for streams that use + * [2^minimum_account_to_track_power_of_two, + * 2^(minimum_account_to_track_power_of_two + 1)) bytes. + * With the 8th bucket tracking accounts + * >= 128 * 2^minimum_account_to_track_power_of_two. + * + * The maximum value is 56, since we're using uint64_t for bytes counting, + * and that's the last value that would use the 8 buckets. In practice, + * we don't expect the proxy to be holding 2^56 bytes. + * + * If omitted, Envoy should not do any tracking. + */ + 'minimum_account_to_track_power_of_two'?: (number); +} + +/** + * Configuration for which accounts the WatermarkBuffer Factories should + * track. + */ +export interface BufferFactoryConfig__Output { + /** + * The minimum power of two at which Envoy starts tracking an account. + * + * Envoy has 8 power of two buckets starting with the provided exponent below. + * Concretely the 1st bucket contains accounts for streams that use + * [2^minimum_account_to_track_power_of_two, + * 2^(minimum_account_to_track_power_of_two + 1)) bytes. + * With the 8th bucket tracking accounts + * >= 128 * 2^minimum_account_to_track_power_of_two. + * + * The maximum value is 56, since we're using uint64_t for bytes counting, + * and that's the last value that would use the 8 buckets. In practice, + * we don't expect the proxy to be holding 2^56 bytes. + * + * If omitted, Envoy should not do any tracking. + */ + 'minimum_account_to_track_power_of_two': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts new file mode 100644 index 000000000..84f4db34b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts @@ -0,0 +1,42 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { Trigger as _envoy_config_overload_v3_Trigger, Trigger__Output as _envoy_config_overload_v3_Trigger__Output } from '../../../../envoy/config/overload/v3/Trigger'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +export interface OverloadAction { + /** + * The name of the overload action. This is just a well-known string that listeners can + * use for registering callbacks. Custom overload actions should be named using reverse + * DNS to ensure uniqueness. + */ + 'name'?: (string); + /** + * A set of triggers for this action. The state of the action is the maximum + * state of all triggers, which can be scaling between 0 and 1 or saturated. Listeners + * are notified when the overload action changes state. + */ + 'triggers'?: (_envoy_config_overload_v3_Trigger)[]; + /** + * Configuration for the action being instantiated. + */ + 'typed_config'?: (_google_protobuf_Any | null); +} + +export interface OverloadAction__Output { + /** + * The name of the overload action. This is just a well-known string that listeners can + * use for registering callbacks. Custom overload actions should be named using reverse + * DNS to ensure uniqueness. + */ + 'name': (string); + /** + * A set of triggers for this action. The state of the action is the maximum + * state of all triggers, which can be scaling between 0 and 1 or saturated. Listeners + * are notified when the overload action changes state. + */ + 'triggers': (_envoy_config_overload_v3_Trigger__Output)[]; + /** + * Configuration for the action being instantiated. + */ + 'typed_config': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts new file mode 100644 index 000000000..e7f75b8e9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts @@ -0,0 +1,44 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { ResourceMonitor as _envoy_config_overload_v3_ResourceMonitor, ResourceMonitor__Output as _envoy_config_overload_v3_ResourceMonitor__Output } from '../../../../envoy/config/overload/v3/ResourceMonitor'; +import type { OverloadAction as _envoy_config_overload_v3_OverloadAction, OverloadAction__Output as _envoy_config_overload_v3_OverloadAction__Output } from '../../../../envoy/config/overload/v3/OverloadAction'; +import type { BufferFactoryConfig as _envoy_config_overload_v3_BufferFactoryConfig, BufferFactoryConfig__Output as _envoy_config_overload_v3_BufferFactoryConfig__Output } from '../../../../envoy/config/overload/v3/BufferFactoryConfig'; + +export interface OverloadManager { + /** + * The interval for refreshing resource usage. + */ + 'refresh_interval'?: (_google_protobuf_Duration | null); + /** + * The set of resources to monitor. + */ + 'resource_monitors'?: (_envoy_config_overload_v3_ResourceMonitor)[]; + /** + * The set of overload actions. + */ + 'actions'?: (_envoy_config_overload_v3_OverloadAction)[]; + /** + * Configuration for buffer factory. + */ + 'buffer_factory_config'?: (_envoy_config_overload_v3_BufferFactoryConfig | null); +} + +export interface OverloadManager__Output { + /** + * The interval for refreshing resource usage. + */ + 'refresh_interval': (_google_protobuf_Duration__Output | null); + /** + * The set of resources to monitor. + */ + 'resource_monitors': (_envoy_config_overload_v3_ResourceMonitor__Output)[]; + /** + * The set of overload actions. + */ + 'actions': (_envoy_config_overload_v3_OverloadAction__Output)[]; + /** + * Configuration for buffer factory. + */ + 'buffer_factory_config': (_envoy_config_overload_v3_BufferFactoryConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts new file mode 100644 index 000000000..02fde2411 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts @@ -0,0 +1,33 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +export interface ResourceMonitor { + /** + * The name of the resource monitor to instantiate. Must match a registered + * resource monitor type. + * See the :ref:`extensions listed in typed_config below ` for the default list of available resource monitor. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Configuration for the resource monitor being instantiated. + * [#extension-category: envoy.resource_monitors] + */ + 'config_type'?: "typed_config"; +} + +export interface ResourceMonitor__Output { + /** + * The name of the resource monitor to instantiate. Must match a registered + * resource monitor type. + * See the :ref:`extensions listed in typed_config below ` for the default list of available resource monitor. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Configuration for the resource monitor being instantiated. + * [#extension-category: envoy.resource_monitors] + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts new file mode 100644 index 000000000..bb48fe3f6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts @@ -0,0 +1,89 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; + +export interface _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer { + /** + * The type of timer this minimum applies to. + */ + 'timer'?: (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType | keyof typeof _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType); + /** + * Sets the minimum duration as an absolute value. + */ + 'min_timeout'?: (_google_protobuf_Duration | null); + /** + * Sets the minimum duration as a percentage of the maximum value. + */ + 'min_scale'?: (_envoy_type_v3_Percent | null); + 'overload_adjust'?: "min_timeout"|"min_scale"; +} + +export interface _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer__Output { + /** + * The type of timer this minimum applies to. + */ + 'timer': (keyof typeof _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType); + /** + * Sets the minimum duration as an absolute value. + */ + 'min_timeout'?: (_google_protobuf_Duration__Output | null); + /** + * Sets the minimum duration as a percentage of the maximum value. + */ + 'min_scale'?: (_envoy_type_v3_Percent__Output | null); + 'overload_adjust': "min_timeout"|"min_scale"; +} + +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +export enum _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType { + /** + * Unsupported value; users must explicitly specify the timer they want scaled. + */ + UNSPECIFIED = 0, + /** + * Adjusts the idle timer for downstream HTTP connections that takes effect when there are no active streams. + * This affects the value of :ref:`HttpConnectionManager.common_http_protocol_options.idle_timeout + * ` + */ + HTTP_DOWNSTREAM_CONNECTION_IDLE = 1, + /** + * Adjusts the idle timer for HTTP streams initiated by downstream clients. + * This affects the value of :ref:`RouteAction.idle_timeout ` and + * :ref:`HttpConnectionManager.stream_idle_timeout + * ` + */ + HTTP_DOWNSTREAM_STREAM_IDLE = 2, + /** + * Adjusts the timer for how long downstream clients have to finish transport-level negotiations + * before the connection is closed. + * This affects the value of + * :ref:`FilterChain.transport_socket_connect_timeout `. + */ + TRANSPORT_SOCKET_CONNECT = 3, +} + +/** + * Typed configuration for the "envoy.overload_actions.reduce_timeouts" action. See + * :ref:`the docs ` for an example of how to configure + * the action with different timeouts and minimum values. + */ +export interface ScaleTimersOverloadActionConfig { + /** + * A set of timer scaling rules to be applied. + */ + 'timer_scale_factors'?: (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer)[]; +} + +/** + * Typed configuration for the "envoy.overload_actions.reduce_timeouts" action. See + * :ref:`the docs ` for an example of how to configure + * the action with different timeouts and minimum values. + */ +export interface ScaleTimersOverloadActionConfig__Output { + /** + * A set of timer scaling rules to be applied. + */ + 'timer_scale_factors': (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts new file mode 100644 index 000000000..8c6574f56 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts @@ -0,0 +1,28 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + + +export interface ScaledTrigger { + /** + * If the resource pressure is greater than this value, the trigger will be in the + * :ref:`scaling ` state with value + * `(pressure - scaling_threshold) / (saturation_threshold - scaling_threshold)`. + */ + 'scaling_threshold'?: (number | string); + /** + * If the resource pressure is greater than this value, the trigger will enter saturation. + */ + 'saturation_threshold'?: (number | string); +} + +export interface ScaledTrigger__Output { + /** + * If the resource pressure is greater than this value, the trigger will be in the + * :ref:`scaling ` state with value + * `(pressure - scaling_threshold) / (saturation_threshold - scaling_threshold)`. + */ + 'scaling_threshold': (number); + /** + * If the resource pressure is greater than this value, the trigger will enter saturation. + */ + 'saturation_threshold': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts new file mode 100644 index 000000000..b02ddd47d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts @@ -0,0 +1,18 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + + +export interface ThresholdTrigger { + /** + * If the resource pressure is greater than or equal to this value, the trigger + * will enter saturation. + */ + 'value'?: (number | string); +} + +export interface ThresholdTrigger__Output { + /** + * If the resource pressure is greater than or equal to this value, the trigger + * will enter saturation. + */ + 'value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts new file mode 100644 index 000000000..38f360ee7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts @@ -0,0 +1,24 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { ThresholdTrigger as _envoy_config_overload_v3_ThresholdTrigger, ThresholdTrigger__Output as _envoy_config_overload_v3_ThresholdTrigger__Output } from '../../../../envoy/config/overload/v3/ThresholdTrigger'; +import type { ScaledTrigger as _envoy_config_overload_v3_ScaledTrigger, ScaledTrigger__Output as _envoy_config_overload_v3_ScaledTrigger__Output } from '../../../../envoy/config/overload/v3/ScaledTrigger'; + +export interface Trigger { + /** + * The name of the resource this is a trigger for. + */ + 'name'?: (string); + 'threshold'?: (_envoy_config_overload_v3_ThresholdTrigger | null); + 'scaled'?: (_envoy_config_overload_v3_ScaledTrigger | null); + 'trigger_oneof'?: "threshold"|"scaled"; +} + +export interface Trigger__Output { + /** + * The name of the resource this is a trigger for. + */ + 'name': (string); + 'threshold'?: (_envoy_config_overload_v3_ThresholdTrigger__Output | null); + 'scaled'?: (_envoy_config_overload_v3_ScaledTrigger__Output | null); + 'trigger_oneof': "threshold"|"scaled"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts new file mode 100644 index 000000000..14724412a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts @@ -0,0 +1,23 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route.proto + +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Configuration for a cluster specifier plugin. + */ +export interface ClusterSpecifierPlugin { + /** + * The name of the plugin and its opaque configuration. + */ + 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +/** + * Configuration for a cluster specifier plugin. + */ +export interface ClusterSpecifierPlugin__Output { + /** + * The name of the plugin and its opaque configuration. + */ + 'extension': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts new file mode 100644 index 000000000..40634448a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts @@ -0,0 +1,113 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; + +/** + * [#next-free-field: 12] + */ +export interface CorsPolicy { + /** + * Specifies the content for the *access-control-allow-methods* header. + */ + 'allow_methods'?: (string); + /** + * Specifies the content for the *access-control-allow-headers* header. + */ + 'allow_headers'?: (string); + /** + * Specifies the content for the *access-control-expose-headers* header. + */ + 'expose_headers'?: (string); + /** + * Specifies the content for the *access-control-max-age* header. + */ + 'max_age'?: (string); + /** + * Specifies whether the resource allows credentials. + */ + 'allow_credentials'?: (_google_protobuf_BoolValue | null); + /** + * Specifies the % of requests for which the CORS filter is enabled. + * + * If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS + * filter will be enabled for 100% of the requests. + * + * If :ref:`runtime_key ` is + * specified, Envoy will lookup the runtime key to get the percentage of requests to filter. + */ + 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); + /** + * Specifies the % of requests for which the CORS policies will be evaluated and tracked, but not + * enforced. + * + * This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those + * fields have to explicitly disable the filter in order for this setting to take effect. + * + * If :ref:`runtime_key ` is specified, + * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate + * and track the request's *Origin* to determine if it's valid but will not enforce any policies. + */ + 'shadow_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); + /** + * Specifies string patterns that match allowed origins. An origin is allowed if any of the + * string matchers match. + */ + 'allow_origin_string_match'?: (_envoy_type_matcher_v3_StringMatcher)[]; + 'enabled_specifier'?: "filter_enabled"; +} + +/** + * [#next-free-field: 12] + */ +export interface CorsPolicy__Output { + /** + * Specifies the content for the *access-control-allow-methods* header. + */ + 'allow_methods': (string); + /** + * Specifies the content for the *access-control-allow-headers* header. + */ + 'allow_headers': (string); + /** + * Specifies the content for the *access-control-expose-headers* header. + */ + 'expose_headers': (string); + /** + * Specifies the content for the *access-control-max-age* header. + */ + 'max_age': (string); + /** + * Specifies whether the resource allows credentials. + */ + 'allow_credentials': (_google_protobuf_BoolValue__Output | null); + /** + * Specifies the % of requests for which the CORS filter is enabled. + * + * If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS + * filter will be enabled for 100% of the requests. + * + * If :ref:`runtime_key ` is + * specified, Envoy will lookup the runtime key to get the percentage of requests to filter. + */ + 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); + /** + * Specifies the % of requests for which the CORS policies will be evaluated and tracked, but not + * enforced. + * + * This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those + * fields have to explicitly disable the filter in order for this setting to take effect. + * + * If :ref:`runtime_key ` is specified, + * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate + * and track the request's *Origin* to determine if it's valid but will not enforce any policies. + */ + 'shadow_enabled': (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); + /** + * Specifies string patterns that match allowed origins. An origin is allowed if any of the + * string matchers match. + */ + 'allow_origin_string_match': (_envoy_type_matcher_v3_StringMatcher__Output)[]; + 'enabled_specifier': "filter_enabled"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts new file mode 100644 index 000000000..fa8bef1d8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts @@ -0,0 +1,39 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; + +export interface Decorator { + /** + * The operation name associated with the request matched to this route. If tracing is + * enabled, this information will be used as the span name reported for this request. + * + * .. note:: + * + * For ingress (inbound) requests, or egress (outbound) responses, this value may be overridden + * by the :ref:`x-envoy-decorator-operation + * ` header. + */ + 'operation'?: (string); + /** + * Whether the decorated details should be propagated to the other party. The default is true. + */ + 'propagate'?: (_google_protobuf_BoolValue | null); +} + +export interface Decorator__Output { + /** + * The operation name associated with the request matched to this route. If tracing is + * enabled, this information will be used as the span name reported for this request. + * + * .. note:: + * + * For ingress (inbound) requests, or egress (outbound) responses, this value may be overridden + * by the :ref:`x-envoy-decorator-operation + * ` header. + */ + 'operation': (string); + /** + * Whether the decorated details should be propagated to the other party. The default is true. + */ + 'propagate': (_google_protobuf_BoolValue__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts new file mode 100644 index 000000000..794ae510a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts @@ -0,0 +1,39 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; + +export interface DirectResponseAction { + /** + * Specifies the HTTP response status to be returned. + */ + 'status'?: (number); + /** + * Specifies the content of the response body. If this setting is omitted, + * no body is included in the generated response. + * + * .. note:: + * + * Headers can be specified using *response_headers_to_add* in the enclosing + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or + * :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. + */ + 'body'?: (_envoy_config_core_v3_DataSource | null); +} + +export interface DirectResponseAction__Output { + /** + * Specifies the HTTP response status to be returned. + */ + 'status': (number); + /** + * Specifies the content of the response body. If this setting is omitted, + * no body is included in the generated response. + * + * .. note:: + * + * Headers can be specified using *response_headers_to_add* in the enclosing + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or + * :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. + */ + 'body': (_envoy_config_core_v3_DataSource__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts new file mode 100644 index 000000000..2765c0799 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * A filter-defined action type. + */ +export interface FilterAction { + 'action'?: (_google_protobuf_Any | null); +} + +/** + * A filter-defined action type. + */ +export interface FilterAction__Output { + 'action': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts new file mode 100644 index 000000000..2c960419c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts @@ -0,0 +1,47 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the + * map value in + * :ref:`VirtualHost.typed_per_filter_config`, + * :ref:`Route.typed_per_filter_config`, + * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` + * to add additional flags to the filter. + * [#not-implemented-hide:] + */ +export interface FilterConfig { + /** + * The filter config. + */ + 'config'?: (_google_protobuf_Any | null); + /** + * If true, the filter is optional, meaning that if the client does + * not support the specified filter, it may ignore the map entry rather + * than rejecting the config. + */ + 'is_optional'?: (boolean); +} + +/** + * A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the + * map value in + * :ref:`VirtualHost.typed_per_filter_config`, + * :ref:`Route.typed_per_filter_config`, + * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` + * to add additional flags to the filter. + * [#not-implemented-hide:] + */ +export interface FilterConfig__Output { + /** + * The filter config. + */ + 'config': (_google_protobuf_Any__Output | null); + /** + * If true, the filter is optional, meaning that if the client does + * not support the specified filter, it may ignore the map entry rather + * than rejecting the config. + */ + 'is_optional': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts new file mode 100644 index 000000000..bde8f28cd --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts @@ -0,0 +1,232 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { Int64Range as _envoy_type_v3_Int64Range, Int64Range__Output as _envoy_type_v3_Int64Range__Output } from '../../../../envoy/type/v3/Int64Range'; +import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; +import type { Long } from '@grpc/proto-loader'; + +/** + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 *Host* + * header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. attention:: + * + * To route on HTTP method, use the special HTTP/2 *:method* header. This works for both + * HTTP/1 and HTTP/2 as Envoy normalizes headers. E.g., + * + * .. code-block:: json + * + * { + * "name": ":method", + * "exact_match": "POST" + * } + * + * .. attention:: + * In the absence of any header match specifier, match will default to :ref:`present_match + * `. i.e, a request that has the :ref:`name + * ` header will match, regardless of the header's + * value. + * + * [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] + * [#next-free-field: 14] + */ +export interface HeaderMatcher { + /** + * Specifies the name of the header in the request. + */ + 'name'?: (string); + /** + * If specified, header match will be performed based on the value of the header. + * This field is deprecated. Please use :ref:`string_match `. + */ + 'exact_match'?: (string); + /** + * If specified, header match will be performed based on range. + * The rule will match if the request header value is within this range. + * The entire request header value must represent an integer in base 10 notation: consisting of + * an optional plus or minus sign followed by a sequence of digits. The rule will not match if + * the header value does not represent an integer. Match will fail for empty values, floating + * point numbers or if only a subsequence of the header value is an integer. + * + * Examples: + * + * * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, + * "-1somestring" + */ + 'range_match'?: (_envoy_type_v3_Int64Range | null); + /** + * If specified as true, header match will be performed based on whether the header is in the + * request. If specified as false, header match will be performed based on whether the header is absent. + */ + 'present_match'?: (boolean); + /** + * If specified, the match result will be inverted before checking. Defaults to false. + * + * Examples: + * + * * The regex ``\d{3}`` does not match the value *1234*, so it will match when inverted. + * * The range [-10,0) will match the value -1, so it will not match when inverted. + */ + 'invert_match'?: (boolean); + /** + * If specified, header match will be performed based on the prefix of the header value. + * Note: empty prefix is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. + * + * Examples: + * + * * The prefix *abcd* matches the value *abcdxyz*, but not for *abcxyz*. + */ + 'prefix_match'?: (string); + /** + * If specified, header match will be performed based on the suffix of the header value. + * Note: empty suffix is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. + * + * Examples: + * + * * The suffix *abcd* matches the value *xyzabcd*, but not for *xyzbcd*. + */ + 'suffix_match'?: (string); + /** + * If specified, this regex string is a regular expression rule which implies the entire request + * header value must match the regex. The rule will not match if only a subsequence of the + * request header value matches the regex. + * This field is deprecated. Please use :ref:`string_match `. + */ + 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher | null); + /** + * If specified, header match will be performed based on whether the header value contains + * the given value or not. + * Note: empty contains match is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. + * + * Examples: + * + * * The value *abcd* matches the value *xyzabcdpqr*, but not for *xyzbcdpqr*. + */ + 'contains_match'?: (string); + /** + * If specified, header match will be performed based on the string match of the header value. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * Specifies how the header match will be performed to route the request. + */ + 'header_match_specifier'?: "exact_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"|"contains_match"|"string_match"; +} + +/** + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 *Host* + * header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. attention:: + * + * To route on HTTP method, use the special HTTP/2 *:method* header. This works for both + * HTTP/1 and HTTP/2 as Envoy normalizes headers. E.g., + * + * .. code-block:: json + * + * { + * "name": ":method", + * "exact_match": "POST" + * } + * + * .. attention:: + * In the absence of any header match specifier, match will default to :ref:`present_match + * `. i.e, a request that has the :ref:`name + * ` header will match, regardless of the header's + * value. + * + * [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] + * [#next-free-field: 14] + */ +export interface HeaderMatcher__Output { + /** + * Specifies the name of the header in the request. + */ + 'name': (string); + /** + * If specified, header match will be performed based on the value of the header. + * This field is deprecated. Please use :ref:`string_match `. + */ + 'exact_match'?: (string); + /** + * If specified, header match will be performed based on range. + * The rule will match if the request header value is within this range. + * The entire request header value must represent an integer in base 10 notation: consisting of + * an optional plus or minus sign followed by a sequence of digits. The rule will not match if + * the header value does not represent an integer. Match will fail for empty values, floating + * point numbers or if only a subsequence of the header value is an integer. + * + * Examples: + * + * * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, + * "-1somestring" + */ + 'range_match'?: (_envoy_type_v3_Int64Range__Output | null); + /** + * If specified as true, header match will be performed based on whether the header is in the + * request. If specified as false, header match will be performed based on whether the header is absent. + */ + 'present_match'?: (boolean); + /** + * If specified, the match result will be inverted before checking. Defaults to false. + * + * Examples: + * + * * The regex ``\d{3}`` does not match the value *1234*, so it will match when inverted. + * * The range [-10,0) will match the value -1, so it will not match when inverted. + */ + 'invert_match': (boolean); + /** + * If specified, header match will be performed based on the prefix of the header value. + * Note: empty prefix is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. + * + * Examples: + * + * * The prefix *abcd* matches the value *abcdxyz*, but not for *abcxyz*. + */ + 'prefix_match'?: (string); + /** + * If specified, header match will be performed based on the suffix of the header value. + * Note: empty suffix is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. + * + * Examples: + * + * * The suffix *abcd* matches the value *xyzabcd*, but not for *xyzbcd*. + */ + 'suffix_match'?: (string); + /** + * If specified, this regex string is a regular expression rule which implies the entire request + * header value must match the regex. The rule will not match if only a subsequence of the + * request header value matches the regex. + * This field is deprecated. Please use :ref:`string_match `. + */ + 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); + /** + * If specified, header match will be performed based on whether the header value contains + * the given value or not. + * Note: empty contains match is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. + * + * Examples: + * + * * The value *abcd* matches the value *xyzabcdpqr*, but not for *xyzbcdpqr*. + */ + 'contains_match'?: (string); + /** + * If specified, header match will be performed based on the string match of the header value. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * Specifies how the header match will be performed to route the request. + */ + 'header_match_specifier': "exact_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"|"contains_match"|"string_match"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts new file mode 100644 index 000000000..302b6d284 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts @@ -0,0 +1,76 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; + +/** + * HTTP request hedging :ref:`architecture overview `. + */ +export interface HedgePolicy { + /** + * Specifies the number of initial requests that should be sent upstream. + * Must be at least 1. + * Defaults to 1. + * [#not-implemented-hide:] + */ + 'initial_requests'?: (_google_protobuf_UInt32Value | null); + /** + * Specifies a probability that an additional upstream request should be sent + * on top of what is specified by initial_requests. + * Defaults to 0. + * [#not-implemented-hide:] + */ + 'additional_request_chance'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Indicates that a hedged request should be sent when the per-try timeout is hit. + * This means that a retry will be issued without resetting the original request, leaving multiple upstream requests in flight. + * The first request to complete successfully will be the one returned to the caller. + * + * * At any time, a successful response (i.e. not triggering any of the retry-on conditions) would be returned to the client. + * * Before per-try timeout, an error response (per retry-on conditions) would be retried immediately or returned ot the client + * if there are no more retries left. + * * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. + * + * Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + * one error code and specifies a maximum number of retries. + * + * Defaults to false. + */ + 'hedge_on_per_try_timeout'?: (boolean); +} + +/** + * HTTP request hedging :ref:`architecture overview `. + */ +export interface HedgePolicy__Output { + /** + * Specifies the number of initial requests that should be sent upstream. + * Must be at least 1. + * Defaults to 1. + * [#not-implemented-hide:] + */ + 'initial_requests': (_google_protobuf_UInt32Value__Output | null); + /** + * Specifies a probability that an additional upstream request should be sent + * on top of what is specified by initial_requests. + * Defaults to 0. + * [#not-implemented-hide:] + */ + 'additional_request_chance': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Indicates that a hedged request should be sent when the per-try timeout is hit. + * This means that a retry will be issued without resetting the original request, leaving multiple upstream requests in flight. + * The first request to complete successfully will be the one returned to the caller. + * + * * At any time, a successful response (i.e. not triggering any of the retry-on conditions) would be returned to the client. + * * Before per-try timeout, an error response (per retry-on conditions) would be retried immediately or returned ot the client + * if there are no more retries left. + * * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. + * + * Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + * one error code and specifies a maximum number of retries. + * + * Defaults to false. + */ + 'hedge_on_per_try_timeout': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts new file mode 100644 index 000000000..ab74df94e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts @@ -0,0 +1,74 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * HTTP Internal Redirect :ref:`architecture overview `. + */ +export interface InternalRedirectPolicy { + /** + * An internal redirect is not handled, unless the number of previous internal redirects that a + * downstream request has encountered is lower than this value. + * In the case where a downstream request is bounced among multiple routes by internal redirect, + * the first route that hits this threshold, or does not set :ref:`internal_redirect_policy + * ` + * will pass the redirect back to downstream. + * + * If not specified, at most one redirect will be followed. + */ + 'max_internal_redirects'?: (_google_protobuf_UInt32Value | null); + /** + * Defines what upstream response codes are allowed to trigger internal redirect. If unspecified, + * only 302 will be treated as internal redirect. + * Only 301, 302, 303, 307 and 308 are valid values. Any other codes will be ignored. + */ + 'redirect_response_codes'?: (number)[]; + /** + * Specifies a list of predicates that are queried when an upstream response is deemed + * to trigger an internal redirect by all other criteria. Any predicate in the list can reject + * the redirect, causing the response to be proxied to downstream. + * [#extension-category: envoy.internal_redirect_predicates] + */ + 'predicates'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Allow internal redirect to follow a target URI with a different scheme than the value of + * x-forwarded-proto. The default is false. + */ + 'allow_cross_scheme_redirect'?: (boolean); +} + +/** + * HTTP Internal Redirect :ref:`architecture overview `. + */ +export interface InternalRedirectPolicy__Output { + /** + * An internal redirect is not handled, unless the number of previous internal redirects that a + * downstream request has encountered is lower than this value. + * In the case where a downstream request is bounced among multiple routes by internal redirect, + * the first route that hits this threshold, or does not set :ref:`internal_redirect_policy + * ` + * will pass the redirect back to downstream. + * + * If not specified, at most one redirect will be followed. + */ + 'max_internal_redirects': (_google_protobuf_UInt32Value__Output | null); + /** + * Defines what upstream response codes are allowed to trigger internal redirect. If unspecified, + * only 302 will be treated as internal redirect. + * Only 301, 302, 303, 307 and 308 are valid values. Any other codes will be ignored. + */ + 'redirect_response_codes': (number)[]; + /** + * Specifies a list of predicates that are queried when an upstream response is deemed + * to trigger an internal redirect by all other criteria. Any predicate in the list can reject + * the redirect, causing the response to be proxied to downstream. + * [#extension-category: envoy.internal_redirect_predicates] + */ + 'predicates': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Allow internal redirect to follow a target URI with a different scheme than the value of + * x-forwarded-proto. The default is false. + */ + 'allow_cross_scheme_redirect': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/NonForwardingAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/NonForwardingAction.ts new file mode 100644 index 000000000..e9c67d44f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/NonForwardingAction.ts @@ -0,0 +1,14 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + + +/** + * [#not-implemented-hide:] + */ +export interface NonForwardingAction { +} + +/** + * [#not-implemented-hide:] + */ +export interface NonForwardingAction__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts new file mode 100644 index 000000000..d511259b0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts @@ -0,0 +1,47 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; + +/** + * Query parameter matching treats the query string of a request's :path header + * as an ampersand-separated list of keys and/or key=value elements. + * [#next-free-field: 7] + */ +export interface QueryParameterMatcher { + /** + * Specifies the name of a key that must be present in the requested + * *path*'s query string. + */ + 'name'?: (string); + /** + * Specifies whether a query parameter value should match against a string. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * Specifies whether a query parameter should be present. + */ + 'present_match'?: (boolean); + 'query_parameter_match_specifier'?: "string_match"|"present_match"; +} + +/** + * Query parameter matching treats the query string of a request's :path header + * as an ampersand-separated list of keys and/or key=value elements. + * [#next-free-field: 7] + */ +export interface QueryParameterMatcher__Output { + /** + * Specifies the name of a key that must be present in the requested + * *path*'s query string. + */ + 'name': (string); + /** + * Specifies whether a query parameter value should match against a string. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * Specifies whether a query parameter should be present. + */ + 'present_match'?: (boolean); + 'query_parameter_match_specifier': "string_match"|"present_match"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts new file mode 100644 index 000000000..f1d49537c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts @@ -0,0 +1,580 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; +import type { MetadataKey as _envoy_type_metadata_v3_MetadataKey, MetadataKey__Output as _envoy_type_metadata_v3_MetadataKey__Output } from '../../../../envoy/type/metadata/v3/MetadataKey'; + +/** + * [#next-free-field: 10] + */ +export interface _envoy_config_route_v3_RateLimit_Action { + /** + * Rate limit on source cluster. + */ + 'source_cluster'?: (_envoy_config_route_v3_RateLimit_Action_SourceCluster | null); + /** + * Rate limit on destination cluster. + */ + 'destination_cluster'?: (_envoy_config_route_v3_RateLimit_Action_DestinationCluster | null); + /** + * Rate limit on request headers. + */ + 'request_headers'?: (_envoy_config_route_v3_RateLimit_Action_RequestHeaders | null); + /** + * Rate limit on remote address. + */ + 'remote_address'?: (_envoy_config_route_v3_RateLimit_Action_RemoteAddress | null); + /** + * Rate limit on a generic key. + */ + 'generic_key'?: (_envoy_config_route_v3_RateLimit_Action_GenericKey | null); + /** + * Rate limit on the existence of request headers. + */ + 'header_value_match'?: (_envoy_config_route_v3_RateLimit_Action_HeaderValueMatch | null); + /** + * Rate limit on dynamic metadata. + * + * .. attention:: + * This field has been deprecated in favor of the :ref:`metadata ` field + */ + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData | null); + /** + * Rate limit on metadata. + */ + 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData | null); + /** + * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + * [#extension-category: envoy.rate_limit_descriptors] + */ + 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + 'action_specifier'?: "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; +} + +/** + * [#next-free-field: 10] + */ +export interface _envoy_config_route_v3_RateLimit_Action__Output { + /** + * Rate limit on source cluster. + */ + 'source_cluster'?: (_envoy_config_route_v3_RateLimit_Action_SourceCluster__Output | null); + /** + * Rate limit on destination cluster. + */ + 'destination_cluster'?: (_envoy_config_route_v3_RateLimit_Action_DestinationCluster__Output | null); + /** + * Rate limit on request headers. + */ + 'request_headers'?: (_envoy_config_route_v3_RateLimit_Action_RequestHeaders__Output | null); + /** + * Rate limit on remote address. + */ + 'remote_address'?: (_envoy_config_route_v3_RateLimit_Action_RemoteAddress__Output | null); + /** + * Rate limit on a generic key. + */ + 'generic_key'?: (_envoy_config_route_v3_RateLimit_Action_GenericKey__Output | null); + /** + * Rate limit on the existence of request headers. + */ + 'header_value_match'?: (_envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Output | null); + /** + * Rate limit on dynamic metadata. + * + * .. attention:: + * This field has been deprecated in favor of the :ref:`metadata ` field + */ + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output | null); + /** + * Rate limit on metadata. + */ + 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData__Output | null); + /** + * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + * [#extension-category: envoy.rate_limit_descriptors] + */ + 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + 'action_specifier': "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("destination_cluster", "") + * + * Once a request matches against a route table rule, a routed cluster is determined by one of + * the following :ref:`route table configuration ` + * settings: + * + * * :ref:`cluster ` indicates the upstream cluster + * to route to. + * * :ref:`weighted_clusters ` + * chooses a cluster randomly from a set of clusters with attributed weight. + * * :ref:`cluster_header ` indicates which + * header in the request contains the target cluster. + */ +export interface _envoy_config_route_v3_RateLimit_Action_DestinationCluster { +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("destination_cluster", "") + * + * Once a request matches against a route table rule, a routed cluster is determined by one of + * the following :ref:`route table configuration ` + * settings: + * + * * :ref:`cluster ` indicates the upstream cluster + * to route to. + * * :ref:`weighted_clusters ` + * chooses a cluster randomly from a set of clusters with attributed weight. + * * :ref:`cluster_header ` indicates which + * header in the request contains the target cluster. + */ +export interface _envoy_config_route_v3_RateLimit_Action_DestinationCluster__Output { +} + +/** + * The following descriptor entry is appended when the + * :ref:`dynamic metadata ` contains a key value: + * + * .. code-block:: cpp + * + * ("", "") + * + * .. attention:: + * This action has been deprecated in favor of the :ref:`metadata ` action + */ +export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData { + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key'?: (string); + /** + * Metadata struct that defines the key and path to retrieve the string value. A match will + * only happen if the value in the dynamic metadata is of type string. + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); + /** + * An optional value to use if *metadata_key* is empty. If not set and + * no value is present under the metadata_key then no descriptor is generated. + */ + 'default_value'?: (string); +} + +/** + * The following descriptor entry is appended when the + * :ref:`dynamic metadata ` contains a key value: + * + * .. code-block:: cpp + * + * ("", "") + * + * .. attention:: + * This action has been deprecated in favor of the :ref:`metadata ` action + */ +export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output { + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key': (string); + /** + * Metadata struct that defines the key and path to retrieve the string value. A match will + * only happen if the value in the dynamic metadata is of type string. + */ + 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); + /** + * An optional value to use if *metadata_key* is empty. If not set and + * no value is present under the metadata_key then no descriptor is generated. + */ + 'default_value': (string); +} + +/** + * Fetches the override from the dynamic metadata. + */ +export interface _envoy_config_route_v3_RateLimit_Override_DynamicMetadata { + /** + * Metadata struct that defines the key and path to retrieve the struct value. + * The value must be a struct containing an integer "requests_per_unit" property + * and a "unit" property with a value parseable to :ref:`RateLimitUnit + * enum ` + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); +} + +/** + * Fetches the override from the dynamic metadata. + */ +export interface _envoy_config_route_v3_RateLimit_Override_DynamicMetadata__Output { + /** + * Metadata struct that defines the key and path to retrieve the struct value. + * The value must be a struct containing an integer "requests_per_unit" property + * and a "unit" property with a value parseable to :ref:`RateLimitUnit + * enum ` + */ + 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("generic_key", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_GenericKey { + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value'?: (string); + /** + * An optional key to use in the descriptor entry. If not set it defaults + * to 'generic_key' as the descriptor key. + */ + 'descriptor_key'?: (string); +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("generic_key", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_GenericKey__Output { + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value': (string); + /** + * An optional key to use in the descriptor entry. If not set it defaults + * to 'generic_key' as the descriptor key. + */ + 'descriptor_key': (string); +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("header_match", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch { + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value'?: (string); + /** + * If set to true, the action will append a descriptor entry when the + * request matches the headers. If set to false, the action will append a + * descriptor entry when the request does not match the headers. The + * default value is true. + */ + 'expect_match'?: (_google_protobuf_BoolValue | null); + /** + * Specifies a set of headers that the rate limit action should match + * on. The action will check the request’s headers against all the + * specified headers in the config. A match will happen if all the + * headers in the config are present in the request with the same values + * (or based on presence if the value field is not in the config). + */ + 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("header_match", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Output { + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value': (string); + /** + * If set to true, the action will append a descriptor entry when the + * request matches the headers. If set to false, the action will append a + * descriptor entry when the request does not match the headers. The + * default value is true. + */ + 'expect_match': (_google_protobuf_BoolValue__Output | null); + /** + * Specifies a set of headers that the rate limit action should match + * on. The action will check the request’s headers against all the + * specified headers in the config. A match will happen if all the + * headers in the config are present in the request with the same values + * (or based on presence if the value field is not in the config). + */ + 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; +} + +/** + * The following descriptor entry is appended when the metadata contains a key value: + * + * .. code-block:: cpp + * + * ("", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_MetaData { + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key'?: (string); + /** + * Metadata struct that defines the key and path to retrieve the string value. A match will + * only happen if the value in the metadata is of type string. + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); + /** + * An optional value to use if *metadata_key* is empty. If not set and + * no value is present under the metadata_key then no descriptor is generated. + */ + 'default_value'?: (string); + /** + * Source of metadata + */ + 'source'?: (_envoy_config_route_v3_RateLimit_Action_MetaData_Source | keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source); +} + +/** + * The following descriptor entry is appended when the metadata contains a key value: + * + * .. code-block:: cpp + * + * ("", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_MetaData__Output { + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key': (string); + /** + * Metadata struct that defines the key and path to retrieve the string value. A match will + * only happen if the value in the metadata is of type string. + */ + 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); + /** + * An optional value to use if *metadata_key* is empty. If not set and + * no value is present under the metadata_key then no descriptor is generated. + */ + 'default_value': (string); + /** + * Source of metadata + */ + 'source': (keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source); +} + +export interface _envoy_config_route_v3_RateLimit_Override { + /** + * Limit override from dynamic metadata. + */ + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Override_DynamicMetadata | null); + 'override_specifier'?: "dynamic_metadata"; +} + +export interface _envoy_config_route_v3_RateLimit_Override__Output { + /** + * Limit override from dynamic metadata. + */ + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Override_DynamicMetadata__Output | null); + 'override_specifier': "dynamic_metadata"; +} + +/** + * The following descriptor entry is appended to the descriptor and is populated using the + * trusted address from :ref:`x-forwarded-for `: + * + * .. code-block:: cpp + * + * ("remote_address", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_RemoteAddress { +} + +/** + * The following descriptor entry is appended to the descriptor and is populated using the + * trusted address from :ref:`x-forwarded-for `: + * + * .. code-block:: cpp + * + * ("remote_address", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_RemoteAddress__Output { +} + +/** + * The following descriptor entry is appended when a header contains a key that matches the + * *header_name*: + * + * .. code-block:: cpp + * + * ("", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_RequestHeaders { + /** + * The header name to be queried from the request headers. The header’s + * value is used to populate the value of the descriptor entry for the + * descriptor_key. + */ + 'header_name'?: (string); + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key'?: (string); + /** + * If set to true, Envoy skips the descriptor while calling rate limiting service + * when header is not present in the request. By default it skips calling the + * rate limiting service if this header is not present in the request. + */ + 'skip_if_absent'?: (boolean); +} + +/** + * The following descriptor entry is appended when a header contains a key that matches the + * *header_name*: + * + * .. code-block:: cpp + * + * ("", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_RequestHeaders__Output { + /** + * The header name to be queried from the request headers. The header’s + * value is used to populate the value of the descriptor entry for the + * descriptor_key. + */ + 'header_name': (string); + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key': (string); + /** + * If set to true, Envoy skips the descriptor while calling rate limiting service + * when header is not present in the request. By default it skips calling the + * rate limiting service if this header is not present in the request. + */ + 'skip_if_absent': (boolean); +} + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +export enum _envoy_config_route_v3_RateLimit_Action_MetaData_Source { + /** + * Query :ref:`dynamic metadata ` + */ + DYNAMIC = 0, + /** + * Query :ref:`route entry metadata ` + */ + ROUTE_ENTRY = 1, +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("source_cluster", "") + * + * is derived from the :option:`--service-cluster` option. + */ +export interface _envoy_config_route_v3_RateLimit_Action_SourceCluster { +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("source_cluster", "") + * + * is derived from the :option:`--service-cluster` option. + */ +export interface _envoy_config_route_v3_RateLimit_Action_SourceCluster__Output { +} + +/** + * Global rate limiting :ref:`architecture overview `. + * Also applies to Local rate limiting :ref:`using descriptors `. + */ +export interface RateLimit { + /** + * Refers to the stage set in the filter. The rate limit configuration only + * applies to filters with the same stage number. The default stage number is + * 0. + * + * .. note:: + * + * The filter supports a range of 0 - 10 inclusively for stage numbers. + */ + 'stage'?: (_google_protobuf_UInt32Value | null); + /** + * The key to be set in runtime to disable this rate limit configuration. + */ + 'disable_key'?: (string); + /** + * A list of actions that are to be applied for this rate limit configuration. + * Order matters as the actions are processed sequentially and the descriptor + * is composed by appending descriptor entries in that sequence. If an action + * cannot append a descriptor entry, no descriptor is generated for the + * configuration. See :ref:`composing actions + * ` for additional documentation. + */ + 'actions'?: (_envoy_config_route_v3_RateLimit_Action)[]; + /** + * An optional limit override to be appended to the descriptor produced by this + * rate limit configuration. If the override value is invalid or cannot be resolved + * from metadata, no override is provided. See :ref:`rate limit override + * ` for more information. + */ + 'limit'?: (_envoy_config_route_v3_RateLimit_Override | null); +} + +/** + * Global rate limiting :ref:`architecture overview `. + * Also applies to Local rate limiting :ref:`using descriptors `. + */ +export interface RateLimit__Output { + /** + * Refers to the stage set in the filter. The rate limit configuration only + * applies to filters with the same stage number. The default stage number is + * 0. + * + * .. note:: + * + * The filter supports a range of 0 - 10 inclusively for stage numbers. + */ + 'stage': (_google_protobuf_UInt32Value__Output | null); + /** + * The key to be set in runtime to disable this rate limit configuration. + */ + 'disable_key': (string); + /** + * A list of actions that are to be applied for this rate limit configuration. + * Order matters as the actions are processed sequentially and the descriptor + * is composed by appending descriptor entries in that sequence. If an action + * cannot append a descriptor entry, no descriptor is generated for the + * configuration. See :ref:`composing actions + * ` for additional documentation. + */ + 'actions': (_envoy_config_route_v3_RateLimit_Action__Output)[]; + /** + * An optional limit override to be appended to the descriptor produced by this + * rate limit configuration. If the override value is invalid or cannot be resolved + * from metadata, no override is provided. See :ref:`rate limit override + * ` for more information. + */ + 'limit': (_envoy_config_route_v3_RateLimit_Override__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts new file mode 100644 index 000000000..e6d41fd7b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts @@ -0,0 +1,222 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { RegexMatchAndSubstitute as _envoy_type_matcher_v3_RegexMatchAndSubstitute, RegexMatchAndSubstitute__Output as _envoy_type_matcher_v3_RegexMatchAndSubstitute__Output } from '../../../../envoy/type/matcher/v3/RegexMatchAndSubstitute'; + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +export enum _envoy_config_route_v3_RedirectAction_RedirectResponseCode { + /** + * Moved Permanently HTTP Status Code - 301. + */ + MOVED_PERMANENTLY = 0, + /** + * Found HTTP Status Code - 302. + */ + FOUND = 1, + /** + * See Other HTTP Status Code - 303. + */ + SEE_OTHER = 2, + /** + * Temporary Redirect HTTP Status Code - 307. + */ + TEMPORARY_REDIRECT = 3, + /** + * Permanent Redirect HTTP Status Code - 308. + */ + PERMANENT_REDIRECT = 4, +} + +/** + * [#next-free-field: 10] + */ +export interface RedirectAction { + /** + * The host portion of the URL will be swapped with this value. + */ + 'host_redirect'?: (string); + /** + * The path portion of the URL will be swapped with this value. + * Please note that query string in path_redirect will override the + * request's query string and will not be stripped. + * + * For example, let's say we have the following routes: + * + * - match: { path: "/old-path-1" } + * redirect: { path_redirect: "/new-path-1" } + * - match: { path: "/old-path-2" } + * redirect: { path_redirect: "/new-path-2", strip-query: "true" } + * - match: { path: "/old-path-3" } + * redirect: { path_redirect: "/new-path-3?foo=1", strip_query: "true" } + * + * 1. if request uri is "/old-path-1?bar=1", users will be redirected to "/new-path-1?bar=1" + * 2. if request uri is "/old-path-2?bar=1", users will be redirected to "/new-path-2" + * 3. if request uri is "/old-path-3?bar=1", users will be redirected to "/new-path-3?foo=1" + */ + 'path_redirect'?: (string); + /** + * The HTTP status code to use in the redirect response. The default response + * code is MOVED_PERMANENTLY (301). + */ + 'response_code'?: (_envoy_config_route_v3_RedirectAction_RedirectResponseCode | keyof typeof _envoy_config_route_v3_RedirectAction_RedirectResponseCode); + /** + * The scheme portion of the URL will be swapped with "https". + */ + 'https_redirect'?: (boolean); + /** + * Indicates that during redirection, the matched prefix (or path) + * should be swapped with this value. This option allows redirect URLs be dynamically created + * based on the request. + * + * .. attention:: + * + * Pay attention to the use of trailing slashes as mentioned in + * :ref:`RouteAction's prefix_rewrite `. + */ + 'prefix_rewrite'?: (string); + /** + * Indicates that during redirection, the query portion of the URL will + * be removed. Default value is false. + */ + 'strip_query'?: (boolean); + /** + * The scheme portion of the URL will be swapped with this value. + */ + 'scheme_redirect'?: (string); + /** + * The port value of the URL will be swapped with this value. + */ + 'port_redirect'?: (number); + /** + * Indicates that during redirect, portions of the path that match the + * pattern should be rewritten, even allowing the substitution of capture + * groups from the pattern into the new path as specified by the rewrite + * substitution string. This is useful to allow application paths to be + * rewritten in a way that is aware of segments with variable content like + * identifiers. + * + * Examples using Google's `RE2 `_ engine: + * + * * The path pattern ``^/service/([^/]+)(/.*)$`` paired with a substitution + * string of ``\2/instance/\1`` would transform ``/service/foo/v1/api`` + * into ``/v1/api/instance/foo``. + * + * * The pattern ``one`` paired with a substitution string of ``two`` would + * transform ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/two/zzz``. + * + * * The pattern ``^(.*?)one(.*)$`` paired with a substitution string of + * ``\1two\2`` would replace only the first occurrence of ``one``, + * transforming path ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/one/zzz``. + * + * * The pattern ``(?i)/xxx/`` paired with a substitution string of ``/yyy/`` + * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to + * ``/aaa/yyy/bbb``. + */ + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); + /** + * When the scheme redirection take place, the following rules apply: + * 1. If the source URI scheme is `http` and the port is explicitly + * set to `:80`, the port will be removed after the redirection + * 2. If the source URI scheme is `https` and the port is explicitly + * set to `:443`, the port will be removed after the redirection + */ + 'scheme_rewrite_specifier'?: "https_redirect"|"scheme_redirect"; + 'path_rewrite_specifier'?: "path_redirect"|"prefix_rewrite"|"regex_rewrite"; +} + +/** + * [#next-free-field: 10] + */ +export interface RedirectAction__Output { + /** + * The host portion of the URL will be swapped with this value. + */ + 'host_redirect': (string); + /** + * The path portion of the URL will be swapped with this value. + * Please note that query string in path_redirect will override the + * request's query string and will not be stripped. + * + * For example, let's say we have the following routes: + * + * - match: { path: "/old-path-1" } + * redirect: { path_redirect: "/new-path-1" } + * - match: { path: "/old-path-2" } + * redirect: { path_redirect: "/new-path-2", strip-query: "true" } + * - match: { path: "/old-path-3" } + * redirect: { path_redirect: "/new-path-3?foo=1", strip_query: "true" } + * + * 1. if request uri is "/old-path-1?bar=1", users will be redirected to "/new-path-1?bar=1" + * 2. if request uri is "/old-path-2?bar=1", users will be redirected to "/new-path-2" + * 3. if request uri is "/old-path-3?bar=1", users will be redirected to "/new-path-3?foo=1" + */ + 'path_redirect'?: (string); + /** + * The HTTP status code to use in the redirect response. The default response + * code is MOVED_PERMANENTLY (301). + */ + 'response_code': (keyof typeof _envoy_config_route_v3_RedirectAction_RedirectResponseCode); + /** + * The scheme portion of the URL will be swapped with "https". + */ + 'https_redirect'?: (boolean); + /** + * Indicates that during redirection, the matched prefix (or path) + * should be swapped with this value. This option allows redirect URLs be dynamically created + * based on the request. + * + * .. attention:: + * + * Pay attention to the use of trailing slashes as mentioned in + * :ref:`RouteAction's prefix_rewrite `. + */ + 'prefix_rewrite'?: (string); + /** + * Indicates that during redirection, the query portion of the URL will + * be removed. Default value is false. + */ + 'strip_query': (boolean); + /** + * The scheme portion of the URL will be swapped with this value. + */ + 'scheme_redirect'?: (string); + /** + * The port value of the URL will be swapped with this value. + */ + 'port_redirect': (number); + /** + * Indicates that during redirect, portions of the path that match the + * pattern should be rewritten, even allowing the substitution of capture + * groups from the pattern into the new path as specified by the rewrite + * substitution string. This is useful to allow application paths to be + * rewritten in a way that is aware of segments with variable content like + * identifiers. + * + * Examples using Google's `RE2 `_ engine: + * + * * The path pattern ``^/service/([^/]+)(/.*)$`` paired with a substitution + * string of ``\2/instance/\1`` would transform ``/service/foo/v1/api`` + * into ``/v1/api/instance/foo``. + * + * * The pattern ``one`` paired with a substitution string of ``two`` would + * transform ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/two/zzz``. + * + * * The pattern ``^(.*?)one(.*)$`` paired with a substitution string of + * ``\1two\2`` would replace only the first occurrence of ``one``, + * transforming path ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/one/zzz``. + * + * * The pattern ``(?i)/xxx/`` paired with a substitution string of ``/yyy/`` + * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to + * ``/aaa/yyy/bbb``. + */ + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); + /** + * When the scheme redirection take place, the following rules apply: + * 1. If the source URI scheme is `http` and the port is explicitly + * set to `:80`, the port will be removed after the redirection + * 2. If the source URI scheme is `https` and the port is explicitly + * set to `:443`, the port will be removed after the redirection + */ + 'scheme_rewrite_specifier': "https_redirect"|"scheme_redirect"; + 'path_rewrite_specifier': "path_redirect"|"prefix_rewrite"|"regex_rewrite"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts new file mode 100644 index 000000000..0d523b52e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts @@ -0,0 +1,461 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Long } from '@grpc/proto-loader'; + +/** + * A retry back-off strategy that applies when the upstream server rate limits + * the request. + * + * Given this configuration: + * + * .. code-block:: yaml + * + * rate_limited_retry_back_off: + * reset_headers: + * - name: Retry-After + * format: SECONDS + * - name: X-RateLimit-Reset + * format: UNIX_TIMESTAMP + * max_interval: "300s" + * + * The following algorithm will apply: + * + * 1. If the response contains the header ``Retry-After`` its value must be on + * the form ``120`` (an integer that represents the number of seconds to + * wait before retrying). If so, this value is used as the back-off interval. + * 2. Otherwise, if the response contains the header ``X-RateLimit-Reset`` its + * value must be on the form ``1595320702`` (an integer that represents the + * point in time at which to retry, as a Unix timestamp in seconds). If so, + * the current time is subtracted from this value and the result is used as + * the back-off interval. + * 3. Otherwise, Envoy will use the default + * :ref:`exponential back-off ` + * strategy. + * + * No matter which format is used, if the resulting back-off interval exceeds + * ``max_interval`` it is discarded and the next header in ``reset_headers`` + * is tried. If a request timeout is configured for the route it will further + * limit how long the request will be allowed to run. + * + * To prevent many clients retrying at the same point in time jitter is added + * to the back-off interval, so the resulting interval is decided by taking: + * ``random(interval, interval * 1.5)``. + * + * .. attention:: + * + * Configuring ``rate_limited_retry_back_off`` will not by itself cause a request + * to be retried. You will still need to configure the right retry policy to match + * the responses from the upstream server. + */ +export interface _envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff { + /** + * Specifies the reset headers (like ``Retry-After`` or ``X-RateLimit-Reset``) + * to match against the response. Headers are tried in order, and matched case + * insensitive. The first header to be parsed successfully is used. If no headers + * match the default exponential back-off is used instead. + */ + 'reset_headers'?: (_envoy_config_route_v3_RetryPolicy_ResetHeader)[]; + /** + * Specifies the maximum back off interval that Envoy will allow. If a reset + * header contains an interval longer than this then it will be discarded and + * the next header will be tried. Defaults to 300 seconds. + */ + 'max_interval'?: (_google_protobuf_Duration | null); +} + +/** + * A retry back-off strategy that applies when the upstream server rate limits + * the request. + * + * Given this configuration: + * + * .. code-block:: yaml + * + * rate_limited_retry_back_off: + * reset_headers: + * - name: Retry-After + * format: SECONDS + * - name: X-RateLimit-Reset + * format: UNIX_TIMESTAMP + * max_interval: "300s" + * + * The following algorithm will apply: + * + * 1. If the response contains the header ``Retry-After`` its value must be on + * the form ``120`` (an integer that represents the number of seconds to + * wait before retrying). If so, this value is used as the back-off interval. + * 2. Otherwise, if the response contains the header ``X-RateLimit-Reset`` its + * value must be on the form ``1595320702`` (an integer that represents the + * point in time at which to retry, as a Unix timestamp in seconds). If so, + * the current time is subtracted from this value and the result is used as + * the back-off interval. + * 3. Otherwise, Envoy will use the default + * :ref:`exponential back-off ` + * strategy. + * + * No matter which format is used, if the resulting back-off interval exceeds + * ``max_interval`` it is discarded and the next header in ``reset_headers`` + * is tried. If a request timeout is configured for the route it will further + * limit how long the request will be allowed to run. + * + * To prevent many clients retrying at the same point in time jitter is added + * to the back-off interval, so the resulting interval is decided by taking: + * ``random(interval, interval * 1.5)``. + * + * .. attention:: + * + * Configuring ``rate_limited_retry_back_off`` will not by itself cause a request + * to be retried. You will still need to configure the right retry policy to match + * the responses from the upstream server. + */ +export interface _envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Output { + /** + * Specifies the reset headers (like ``Retry-After`` or ``X-RateLimit-Reset``) + * to match against the response. Headers are tried in order, and matched case + * insensitive. The first header to be parsed successfully is used. If no headers + * match the default exponential back-off is used instead. + */ + 'reset_headers': (_envoy_config_route_v3_RetryPolicy_ResetHeader__Output)[]; + /** + * Specifies the maximum back off interval that Envoy will allow. If a reset + * header contains an interval longer than this then it will be discarded and + * the next header will be tried. Defaults to 300 seconds. + */ + 'max_interval': (_google_protobuf_Duration__Output | null); +} + +export interface _envoy_config_route_v3_RetryPolicy_ResetHeader { + /** + * The name of the reset header. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'name'?: (string); + /** + * The format of the reset header. + */ + 'format'?: (_envoy_config_route_v3_RetryPolicy_ResetHeaderFormat | keyof typeof _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat); +} + +export interface _envoy_config_route_v3_RetryPolicy_ResetHeader__Output { + /** + * The name of the reset header. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'name': (string); + /** + * The format of the reset header. + */ + 'format': (keyof typeof _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat); +} + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +export enum _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat { + SECONDS = 0, + UNIX_TIMESTAMP = 1, +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff { + /** + * Specifies the base interval between retries. This parameter is required and must be greater + * than zero. Values less than 1 ms are rounded up to 1 ms. + * See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's + * back-off algorithm. + */ + 'base_interval'?: (_google_protobuf_Duration | null); + /** + * Specifies the maximum interval between retries. This parameter is optional, but must be + * greater than or equal to the `base_interval` if set. The default is 10 times the + * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion + * of Envoy's back-off algorithm. + */ + 'max_interval'?: (_google_protobuf_Duration | null); +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff__Output { + /** + * Specifies the base interval between retries. This parameter is required and must be greater + * than zero. Values less than 1 ms are rounded up to 1 ms. + * See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's + * back-off algorithm. + */ + 'base_interval': (_google_protobuf_Duration__Output | null); + /** + * Specifies the maximum interval between retries. This parameter is optional, but must be + * greater than or equal to the `base_interval` if set. The default is 10 times the + * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion + * of Envoy's back-off algorithm. + */ + 'max_interval': (_google_protobuf_Duration__Output | null); +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryHostPredicate { + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * [#extension-category: envoy.retry_host_predicates] + */ + 'config_type'?: "typed_config"; +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryHostPredicate__Output { + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * [#extension-category: envoy.retry_host_predicates] + */ + 'config_type': "typed_config"; +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryPriority { + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * [#extension-category: envoy.retry_priorities] + */ + 'config_type'?: "typed_config"; +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryPriority__Output { + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * [#extension-category: envoy.retry_priorities] + */ + 'config_type': "typed_config"; +} + +/** + * HTTP retry :ref:`architecture overview `. + * [#next-free-field: 14] + */ +export interface RetryPolicy { + /** + * Specifies the conditions under which retry takes place. These are the same + * conditions documented for :ref:`config_http_filters_router_x-envoy-retry-on` and + * :ref:`config_http_filters_router_x-envoy-retry-grpc-on`. + */ + 'retry_on'?: (string); + /** + * Specifies the allowed number of retries. This parameter is optional and + * defaults to 1. These are the same conditions documented for + * :ref:`config_http_filters_router_x-envoy-max-retries`. + */ + 'num_retries'?: (_google_protobuf_UInt32Value | null); + /** + * Specifies a non-zero upstream timeout per retry attempt (including the initial attempt). This + * parameter is optional. The same conditions documented for + * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. + * + * .. note:: + * + * If left unspecified, Envoy will use the global + * :ref:`route timeout ` for the request. + * Consequently, when using a :ref:`5xx ` based + * retry policy, a request that times out will not be retried as the total timeout budget + * would have been exhausted. + */ + 'per_try_timeout'?: (_google_protobuf_Duration | null); + /** + * Specifies an implementation of a RetryPriority which is used to determine the + * distribution of load across priorities used for retries. Refer to + * :ref:`retry plugin configuration ` for more details. + */ + 'retry_priority'?: (_envoy_config_route_v3_RetryPolicy_RetryPriority | null); + /** + * Specifies a collection of RetryHostPredicates that will be consulted when selecting a host + * for retries. If any of the predicates reject the host, host selection will be reattempted. + * Refer to :ref:`retry plugin configuration ` for more + * details. + */ + 'retry_host_predicate'?: (_envoy_config_route_v3_RetryPolicy_RetryHostPredicate)[]; + /** + * The maximum number of times host selection will be reattempted before giving up, at which + * point the host that was last selected will be routed to. If unspecified, this will default to + * retrying once. + */ + 'host_selection_retry_max_attempts'?: (number | string | Long); + /** + * HTTP status codes that should trigger a retry in addition to those specified by retry_on. + */ + 'retriable_status_codes'?: (number)[]; + /** + * Specifies parameters that control exponential retry back off. This parameter is optional, in which case the + * default base interval is 25 milliseconds or, if set, the current value of the + * `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times + * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` + * describes Envoy's back-off algorithm. + */ + 'retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RetryBackOff | null); + /** + * HTTP response headers that trigger a retry if present in the response. A retry will be + * triggered if any of the header matches match the upstream response headers. + * The field is only consulted if 'retriable-headers' retry policy is active. + */ + 'retriable_headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; + /** + * HTTP headers which must be present in the request for retries to be attempted. + */ + 'retriable_request_headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; + /** + * Specifies parameters that control a retry back-off strategy that is used + * when the request is rate limited by the upstream server. The server may + * return a response header like ``Retry-After`` or ``X-RateLimit-Reset`` to + * provide feedback to the client on how long to wait before retrying. If + * configured, this back-off strategy will be used instead of the + * default exponential back off strategy (configured using `retry_back_off`) + * whenever a response includes the matching headers. + */ + 'rate_limited_retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff | null); + /** + * Retry options predicates that will be applied prior to retrying a request. These predicates + * allow customizing request behavior between retries. + * [#comment: add [#extension-category: envoy.retry_options_predicates] when there are built-in extensions] + */ + 'retry_options_predicates'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Specifies an upstream idle timeout per retry attempt (including the initial attempt). This + * parameter is optional and if absent there is no per try idle timeout. The semantics of the per + * try idle timeout are similar to the + * :ref:`route idle timeout ` and + * :ref:`stream idle timeout + * ` + * both enforced by the HTTP connection manager. The difference is that this idle timeout + * is enforced by the router for each individual attempt and thus after all previous filters have + * run, as opposed to *before* all previous filters run for the other idle timeouts. This timeout + * is useful in cases in which total request timeout is bounded by a number of retries and a + * :ref:`per_try_timeout `, but + * there is a desire to ensure each try is making incremental progress. Note also that similar + * to :ref:`per_try_timeout `, + * this idle timeout does not start until after both the entire request has been received by the + * router *and* a connection pool connection has been obtained. Unlike + * :ref:`per_try_timeout `, + * the idle timer continues once the response starts streaming back to the downstream client. + * This ensures that response data continues to make progress without using one of the HTTP + * connection manager idle timeouts. + */ + 'per_try_idle_timeout'?: (_google_protobuf_Duration | null); +} + +/** + * HTTP retry :ref:`architecture overview `. + * [#next-free-field: 14] + */ +export interface RetryPolicy__Output { + /** + * Specifies the conditions under which retry takes place. These are the same + * conditions documented for :ref:`config_http_filters_router_x-envoy-retry-on` and + * :ref:`config_http_filters_router_x-envoy-retry-grpc-on`. + */ + 'retry_on': (string); + /** + * Specifies the allowed number of retries. This parameter is optional and + * defaults to 1. These are the same conditions documented for + * :ref:`config_http_filters_router_x-envoy-max-retries`. + */ + 'num_retries': (_google_protobuf_UInt32Value__Output | null); + /** + * Specifies a non-zero upstream timeout per retry attempt (including the initial attempt). This + * parameter is optional. The same conditions documented for + * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. + * + * .. note:: + * + * If left unspecified, Envoy will use the global + * :ref:`route timeout ` for the request. + * Consequently, when using a :ref:`5xx ` based + * retry policy, a request that times out will not be retried as the total timeout budget + * would have been exhausted. + */ + 'per_try_timeout': (_google_protobuf_Duration__Output | null); + /** + * Specifies an implementation of a RetryPriority which is used to determine the + * distribution of load across priorities used for retries. Refer to + * :ref:`retry plugin configuration ` for more details. + */ + 'retry_priority': (_envoy_config_route_v3_RetryPolicy_RetryPriority__Output | null); + /** + * Specifies a collection of RetryHostPredicates that will be consulted when selecting a host + * for retries. If any of the predicates reject the host, host selection will be reattempted. + * Refer to :ref:`retry plugin configuration ` for more + * details. + */ + 'retry_host_predicate': (_envoy_config_route_v3_RetryPolicy_RetryHostPredicate__Output)[]; + /** + * The maximum number of times host selection will be reattempted before giving up, at which + * point the host that was last selected will be routed to. If unspecified, this will default to + * retrying once. + */ + 'host_selection_retry_max_attempts': (string); + /** + * HTTP status codes that should trigger a retry in addition to those specified by retry_on. + */ + 'retriable_status_codes': (number)[]; + /** + * Specifies parameters that control exponential retry back off. This parameter is optional, in which case the + * default base interval is 25 milliseconds or, if set, the current value of the + * `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times + * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` + * describes Envoy's back-off algorithm. + */ + 'retry_back_off': (_envoy_config_route_v3_RetryPolicy_RetryBackOff__Output | null); + /** + * HTTP response headers that trigger a retry if present in the response. A retry will be + * triggered if any of the header matches match the upstream response headers. + * The field is only consulted if 'retriable-headers' retry policy is active. + */ + 'retriable_headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; + /** + * HTTP headers which must be present in the request for retries to be attempted. + */ + 'retriable_request_headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; + /** + * Specifies parameters that control a retry back-off strategy that is used + * when the request is rate limited by the upstream server. The server may + * return a response header like ``Retry-After`` or ``X-RateLimit-Reset`` to + * provide feedback to the client on how long to wait before retrying. If + * configured, this back-off strategy will be used instead of the + * default exponential back off strategy (configured using `retry_back_off`) + * whenever a response includes the matching headers. + */ + 'rate_limited_retry_back_off': (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Output | null); + /** + * Retry options predicates that will be applied prior to retrying a request. These predicates + * allow customizing request behavior between retries. + * [#comment: add [#extension-category: envoy.retry_options_predicates] when there are built-in extensions] + */ + 'retry_options_predicates': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Specifies an upstream idle timeout per retry attempt (including the initial attempt). This + * parameter is optional and if absent there is no per try idle timeout. The semantics of the per + * try idle timeout are similar to the + * :ref:`route idle timeout ` and + * :ref:`stream idle timeout + * ` + * both enforced by the HTTP connection manager. The difference is that this idle timeout + * is enforced by the router for each individual attempt and thus after all previous filters have + * run, as opposed to *before* all previous filters run for the other idle timeouts. This timeout + * is useful in cases in which total request timeout is bounded by a number of retries and a + * :ref:`per_try_timeout `, but + * there is a desire to ensure each try is making incremental progress. Note also that similar + * to :ref:`per_try_timeout `, + * this idle timeout does not start until after both the entire request has been received by the + * router *and* a connection pool connection has been obtained. Unlike + * :ref:`per_try_timeout `, + * the idle timer continues once the response starts streaming back to the downstream client. + * This ensures that response data continues to make progress without using one of the HTTP + * connection manager idle timeouts. + */ + 'per_try_idle_timeout': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts new file mode 100644 index 000000000..d48b554d0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts @@ -0,0 +1,236 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { RouteMatch as _envoy_config_route_v3_RouteMatch, RouteMatch__Output as _envoy_config_route_v3_RouteMatch__Output } from '../../../../envoy/config/route/v3/RouteMatch'; +import type { RouteAction as _envoy_config_route_v3_RouteAction, RouteAction__Output as _envoy_config_route_v3_RouteAction__Output } from '../../../../envoy/config/route/v3/RouteAction'; +import type { RedirectAction as _envoy_config_route_v3_RedirectAction, RedirectAction__Output as _envoy_config_route_v3_RedirectAction__Output } from '../../../../envoy/config/route/v3/RedirectAction'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { Decorator as _envoy_config_route_v3_Decorator, Decorator__Output as _envoy_config_route_v3_Decorator__Output } from '../../../../envoy/config/route/v3/Decorator'; +import type { DirectResponseAction as _envoy_config_route_v3_DirectResponseAction, DirectResponseAction__Output as _envoy_config_route_v3_DirectResponseAction__Output } from '../../../../envoy/config/route/v3/DirectResponseAction'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Tracing as _envoy_config_route_v3_Tracing, Tracing__Output as _envoy_config_route_v3_Tracing__Output } from '../../../../envoy/config/route/v3/Tracing'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { FilterAction as _envoy_config_route_v3_FilterAction, FilterAction__Output as _envoy_config_route_v3_FilterAction__Output } from '../../../../envoy/config/route/v3/FilterAction'; +import type { NonForwardingAction as _envoy_config_route_v3_NonForwardingAction, NonForwardingAction__Output as _envoy_config_route_v3_NonForwardingAction__Output } from '../../../../envoy/config/route/v3/NonForwardingAction'; + +/** + * A route is both a specification of how to match a request as well as an indication of what to do + * next (e.g., redirect, forward, rewrite, etc.). + * + * .. attention:: + * + * Envoy supports routing on HTTP method via :ref:`header matching + * `. + * [#next-free-field: 19] + */ +export interface Route { + /** + * Route matching parameters. + */ + 'match'?: (_envoy_config_route_v3_RouteMatch | null); + /** + * Route request to some upstream cluster. + */ + 'route'?: (_envoy_config_route_v3_RouteAction | null); + /** + * Return a redirect. + */ + 'redirect'?: (_envoy_config_route_v3_RedirectAction | null); + /** + * The Metadata field can be used to provide additional information + * about the route. It can be used for configuration, stats, and logging. + * The metadata should go under the filter namespace that will need it. + * For instance, if the metadata is intended for the Router filter, + * the filter name should be specified as *envoy.filters.http.router*. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata | null); + /** + * Decorator for the matched route. + */ + 'decorator'?: (_envoy_config_route_v3_Decorator | null); + /** + * Return an arbitrary HTTP response directly, without proxying. + */ + 'direct_response'?: (_envoy_config_route_v3_DirectResponseAction | null); + /** + * Specifies a set of headers that will be added to requests matching this + * route. Headers specified at this level are applied before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * Specifies a set of headers that will be added to responses to requests + * matching this route. Headers specified at this level are applied before + * headers from the enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * details on header value syntax, see the documentation on + * :ref:`custom request headers `. + */ + 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * Specifies a list of HTTP headers that should be removed from each response + * to requests matching this route. + */ + 'response_headers_to_remove'?: (string)[]; + /** + * Specifies a list of HTTP headers that should be removed from each request + * matching this route. + */ + 'request_headers_to_remove'?: (string)[]; + /** + * The typed_per_filter_config field can be used to provide route-specific + * configurations for filters. The key should match the filter name, such as + * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter + * specific; see the :ref:`HTTP filter documentation ` for + * if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] + */ + 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); + /** + * Name for the route. + */ + 'name'?: (string); + /** + * Presence of the object defines whether the connection manager's tracing configuration + * is overridden by this route specific instance. + */ + 'tracing'?: (_envoy_config_route_v3_Tracing | null); + /** + * The maximum bytes which will be buffered for retries and shadowing. + * If set, the bytes actually buffered will be the minimum value of this and the + * listener per_connection_buffer_limit_bytes. + */ + 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); + /** + * [#not-implemented-hide:] + * A filter-defined action (e.g., it could dynamically generate the RouteAction). + * [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when + * implemented] + */ + 'filter_action'?: (_envoy_config_route_v3_FilterAction | null); + /** + * [#not-implemented-hide:] + * An action used when the route will generate a response directly, + * without forwarding to an upstream host. This will be used in non-proxy + * xDS clients like the gRPC server. It could also be used in the future + * in Envoy for a filter that directly generates responses for requests. + */ + 'non_forwarding_action'?: (_envoy_config_route_v3_NonForwardingAction | null); + 'action'?: "route"|"redirect"|"direct_response"|"filter_action"|"non_forwarding_action"; +} + +/** + * A route is both a specification of how to match a request as well as an indication of what to do + * next (e.g., redirect, forward, rewrite, etc.). + * + * .. attention:: + * + * Envoy supports routing on HTTP method via :ref:`header matching + * `. + * [#next-free-field: 19] + */ +export interface Route__Output { + /** + * Route matching parameters. + */ + 'match': (_envoy_config_route_v3_RouteMatch__Output | null); + /** + * Route request to some upstream cluster. + */ + 'route'?: (_envoy_config_route_v3_RouteAction__Output | null); + /** + * Return a redirect. + */ + 'redirect'?: (_envoy_config_route_v3_RedirectAction__Output | null); + /** + * The Metadata field can be used to provide additional information + * about the route. It can be used for configuration, stats, and logging. + * The metadata should go under the filter namespace that will need it. + * For instance, if the metadata is intended for the Router filter, + * the filter name should be specified as *envoy.filters.http.router*. + */ + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); + /** + * Decorator for the matched route. + */ + 'decorator': (_envoy_config_route_v3_Decorator__Output | null); + /** + * Return an arbitrary HTTP response directly, without proxying. + */ + 'direct_response'?: (_envoy_config_route_v3_DirectResponseAction__Output | null); + /** + * Specifies a set of headers that will be added to requests matching this + * route. Headers specified at this level are applied before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * Specifies a set of headers that will be added to responses to requests + * matching this route. Headers specified at this level are applied before + * headers from the enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * details on header value syntax, see the documentation on + * :ref:`custom request headers `. + */ + 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * Specifies a list of HTTP headers that should be removed from each response + * to requests matching this route. + */ + 'response_headers_to_remove': (string)[]; + /** + * Specifies a list of HTTP headers that should be removed from each request + * matching this route. + */ + 'request_headers_to_remove': (string)[]; + /** + * The typed_per_filter_config field can be used to provide route-specific + * configurations for filters. The key should match the filter name, such as + * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter + * specific; see the :ref:`HTTP filter documentation ` for + * if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] + */ + 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); + /** + * Name for the route. + */ + 'name': (string); + /** + * Presence of the object defines whether the connection manager's tracing configuration + * is overridden by this route specific instance. + */ + 'tracing': (_envoy_config_route_v3_Tracing__Output | null); + /** + * The maximum bytes which will be buffered for retries and shadowing. + * If set, the bytes actually buffered will be the minimum value of this and the + * listener per_connection_buffer_limit_bytes. + */ + 'per_request_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); + /** + * [#not-implemented-hide:] + * A filter-defined action (e.g., it could dynamically generate the RouteAction). + * [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when + * implemented] + */ + 'filter_action'?: (_envoy_config_route_v3_FilterAction__Output | null); + /** + * [#not-implemented-hide:] + * An action used when the route will generate a response directly, + * without forwarding to an upstream host. This will be used in non-proxy + * xDS clients like the gRPC server. It could also be used in the future + * in Envoy for a filter that directly generates responses for requests. + */ + 'non_forwarding_action'?: (_envoy_config_route_v3_NonForwardingAction__Output | null); + 'action': "route"|"redirect"|"direct_response"|"filter_action"|"non_forwarding_action"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts new file mode 100644 index 000000000..43bd51723 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts @@ -0,0 +1,1177 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { WeightedCluster as _envoy_config_route_v3_WeightedCluster, WeightedCluster__Output as _envoy_config_route_v3_WeightedCluster__Output } from '../../../../envoy/config/route/v3/WeightedCluster'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { RetryPolicy as _envoy_config_route_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_route_v3_RetryPolicy__Output } from '../../../../envoy/config/route/v3/RetryPolicy'; +import type { RoutingPriority as _envoy_config_core_v3_RoutingPriority } from '../../../../envoy/config/core/v3/RoutingPriority'; +import type { RateLimit as _envoy_config_route_v3_RateLimit, RateLimit__Output as _envoy_config_route_v3_RateLimit__Output } from '../../../../envoy/config/route/v3/RateLimit'; +import type { CorsPolicy as _envoy_config_route_v3_CorsPolicy, CorsPolicy__Output as _envoy_config_route_v3_CorsPolicy__Output } from '../../../../envoy/config/route/v3/CorsPolicy'; +import type { HedgePolicy as _envoy_config_route_v3_HedgePolicy, HedgePolicy__Output as _envoy_config_route_v3_HedgePolicy__Output } from '../../../../envoy/config/route/v3/HedgePolicy'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { RegexMatchAndSubstitute as _envoy_type_matcher_v3_RegexMatchAndSubstitute, RegexMatchAndSubstitute__Output as _envoy_type_matcher_v3_RegexMatchAndSubstitute__Output } from '../../../../envoy/type/matcher/v3/RegexMatchAndSubstitute'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { InternalRedirectPolicy as _envoy_config_route_v3_InternalRedirectPolicy, InternalRedirectPolicy__Output as _envoy_config_route_v3_InternalRedirectPolicy__Output } from '../../../../envoy/config/route/v3/InternalRedirectPolicy'; +import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; +import type { ProxyProtocolConfig as _envoy_config_core_v3_ProxyProtocolConfig, ProxyProtocolConfig__Output as _envoy_config_core_v3_ProxyProtocolConfig__Output } from '../../../../envoy/config/core/v3/ProxyProtocolConfig'; + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +export enum _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode { + /** + * HTTP status code - 503 Service Unavailable. + */ + SERVICE_UNAVAILABLE = 0, + /** + * HTTP status code - 404 Not Found. + */ + NOT_FOUND = 1, +} + +/** + * Configuration for sending data upstream as a raw data payload. This is used for + * CONNECT or POST requests, when forwarding request payload as raw TCP. + */ +export interface _envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig { + /** + * If present, the proxy protocol header will be prepended to the CONNECT payload sent upstream. + */ + 'proxy_protocol_config'?: (_envoy_config_core_v3_ProxyProtocolConfig | null); + /** + * If set, the route will also allow forwarding POST payload as raw TCP. + */ + 'allow_post'?: (boolean); +} + +/** + * Configuration for sending data upstream as a raw data payload. This is used for + * CONNECT or POST requests, when forwarding request payload as raw TCP. + */ +export interface _envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig__Output { + /** + * If present, the proxy protocol header will be prepended to the CONNECT payload sent upstream. + */ + 'proxy_protocol_config': (_envoy_config_core_v3_ProxyProtocolConfig__Output | null); + /** + * If set, the route will also allow forwarding POST payload as raw TCP. + */ + 'allow_post': (boolean); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties { + /** + * Hash on source IP address. + */ + 'source_ip'?: (boolean); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties__Output { + /** + * Hash on source IP address. + */ + 'source_ip': (boolean); +} + +/** + * Envoy supports two types of cookie affinity: + * + * 1. Passive. Envoy takes a cookie that's present in the cookies header and + * hashes on its value. + * + * 2. Generated. Envoy generates and sets a cookie with an expiration (TTL) + * on the first request from the client in its response to the client, + * based on the endpoint the request gets sent to. The client then + * presents this on the next and all subsequent requests. The hash of + * this is sufficient to ensure these requests get sent to the same + * endpoint. The cookie is generated by hashing the source and + * destination ports and addresses so that multiple independent HTTP2 + * streams on the same connection will independently receive the same + * cookie, even if they arrive at the Envoy simultaneously. + */ +export interface _envoy_config_route_v3_RouteAction_HashPolicy_Cookie { + /** + * The name of the cookie that will be used to obtain the hash key. If the + * cookie is not present and ttl below is not set, no hash will be + * produced. + */ + 'name'?: (string); + /** + * If specified, a cookie with the TTL will be generated if the cookie is + * not present. If the TTL is present and zero, the generated cookie will + * be a session cookie. + */ + 'ttl'?: (_google_protobuf_Duration | null); + /** + * The name of the path for the cookie. If no path is specified here, no path + * will be set for the cookie. + */ + 'path'?: (string); +} + +/** + * Envoy supports two types of cookie affinity: + * + * 1. Passive. Envoy takes a cookie that's present in the cookies header and + * hashes on its value. + * + * 2. Generated. Envoy generates and sets a cookie with an expiration (TTL) + * on the first request from the client in its response to the client, + * based on the endpoint the request gets sent to. The client then + * presents this on the next and all subsequent requests. The hash of + * this is sufficient to ensure these requests get sent to the same + * endpoint. The cookie is generated by hashing the source and + * destination ports and addresses so that multiple independent HTTP2 + * streams on the same connection will independently receive the same + * cookie, even if they arrive at the Envoy simultaneously. + */ +export interface _envoy_config_route_v3_RouteAction_HashPolicy_Cookie__Output { + /** + * The name of the cookie that will be used to obtain the hash key. If the + * cookie is not present and ttl below is not set, no hash will be + * produced. + */ + 'name': (string); + /** + * If specified, a cookie with the TTL will be generated if the cookie is + * not present. If the TTL is present and zero, the generated cookie will + * be a session cookie. + */ + 'ttl': (_google_protobuf_Duration__Output | null); + /** + * The name of the path for the cookie. If no path is specified here, no path + * will be set for the cookie. + */ + 'path': (string); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_FilterState { + /** + * The name of the Object in the per-request filterState, which is an + * Envoy::Http::Hashable object. If there is no data associated with the key, + * or the stored object is not Envoy::Http::Hashable, no hash will be produced. + */ + 'key'?: (string); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_FilterState__Output { + /** + * The name of the Object in the per-request filterState, which is an + * Envoy::Http::Hashable object. If there is no data associated with the key, + * or the stored object is not Envoy::Http::Hashable, no hash will be produced. + */ + 'key': (string); +} + +/** + * Specifies the route's hashing policy if the upstream cluster uses a hashing :ref:`load balancer + * `. + * [#next-free-field: 7] + */ +export interface _envoy_config_route_v3_RouteAction_HashPolicy { + /** + * Header hash policy. + */ + 'header'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Header | null); + /** + * Cookie hash policy. + */ + 'cookie'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Cookie | null); + /** + * Connection properties hash policy. + */ + 'connection_properties'?: (_envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties | null); + /** + * Query parameter hash policy. + */ + 'query_parameter'?: (_envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter | null); + /** + * Filter state hash policy. + */ + 'filter_state'?: (_envoy_config_route_v3_RouteAction_HashPolicy_FilterState | null); + /** + * The flag that short-circuits the hash computing. This field provides a + * 'fallback' style of configuration: "if a terminal policy doesn't work, + * fallback to rest of the policy list", it saves time when the terminal + * policy works. + * + * If true, and there is already a hash computed, ignore rest of the + * list of hash polices. + * For example, if the following hash methods are configured: + * + * ========= ======== + * specifier terminal + * ========= ======== + * Header A true + * Header B false + * Header C false + * ========= ======== + * + * The generateHash process ends if policy "header A" generates a hash, as + * it's a terminal policy. + */ + 'terminal'?: (boolean); + 'policy_specifier'?: "header"|"cookie"|"connection_properties"|"query_parameter"|"filter_state"; +} + +/** + * Specifies the route's hashing policy if the upstream cluster uses a hashing :ref:`load balancer + * `. + * [#next-free-field: 7] + */ +export interface _envoy_config_route_v3_RouteAction_HashPolicy__Output { + /** + * Header hash policy. + */ + 'header'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Header__Output | null); + /** + * Cookie hash policy. + */ + 'cookie'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Cookie__Output | null); + /** + * Connection properties hash policy. + */ + 'connection_properties'?: (_envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties__Output | null); + /** + * Query parameter hash policy. + */ + 'query_parameter'?: (_envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter__Output | null); + /** + * Filter state hash policy. + */ + 'filter_state'?: (_envoy_config_route_v3_RouteAction_HashPolicy_FilterState__Output | null); + /** + * The flag that short-circuits the hash computing. This field provides a + * 'fallback' style of configuration: "if a terminal policy doesn't work, + * fallback to rest of the policy list", it saves time when the terminal + * policy works. + * + * If true, and there is already a hash computed, ignore rest of the + * list of hash polices. + * For example, if the following hash methods are configured: + * + * ========= ======== + * specifier terminal + * ========= ======== + * Header A true + * Header B false + * Header C false + * ========= ======== + * + * The generateHash process ends if policy "header A" generates a hash, as + * it's a terminal policy. + */ + 'terminal': (boolean); + 'policy_specifier': "header"|"cookie"|"connection_properties"|"query_parameter"|"filter_state"; +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_Header { + /** + * The name of the request header that will be used to obtain the hash + * key. If the request header is not present, no hash will be produced. + */ + 'header_name'?: (string); + /** + * If specified, the request header value will be rewritten and used + * to produce the hash key. + */ + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_Header__Output { + /** + * The name of the request header that will be used to obtain the hash + * key. If the request header is not present, no hash will be produced. + */ + 'header_name': (string); + /** + * If specified, the request header value will be rewritten and used + * to produce the hash key. + */ + 'regex_rewrite': (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); +} + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +/** + * Configures :ref:`internal redirect ` behavior. + * [#next-major-version: remove this definition - it's defined in the InternalRedirectPolicy message.] + */ +export enum _envoy_config_route_v3_RouteAction_InternalRedirectAction { + PASS_THROUGH_INTERNAL_REDIRECT = 0, + HANDLE_INTERNAL_REDIRECT = 1, +} + +export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration { + /** + * Specifies the maximum duration allowed for streams on the route. If not specified, the value + * from the :ref:`max_stream_duration + * ` field in + * :ref:`HttpConnectionManager.common_http_protocol_options + * ` + * is used. If this field is set explicitly to zero, any + * HttpConnectionManager max_stream_duration timeout will be disabled for + * this route. + */ + 'max_stream_duration'?: (_google_protobuf_Duration | null); + /** + * If present, and the request contains a `grpc-timeout header + * `_, use that value as the + * *max_stream_duration*, but limit the applied timeout to the maximum value specified here. + * If set to 0, the `grpc-timeout` header is used without modification. + */ + 'grpc_timeout_header_max'?: (_google_protobuf_Duration | null); + /** + * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by + * subtracting the provided duration from the header. This is useful for allowing Envoy to set + * its global timeout to be less than that of the deadline imposed by the calling client, which + * makes it more likely that Envoy will handle the timeout instead of having the call canceled + * by the client. If, after applying the offset, the resulting timeout is zero or negative, + * the stream will timeout immediately. + */ + 'grpc_timeout_header_offset'?: (_google_protobuf_Duration | null); +} + +export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration__Output { + /** + * Specifies the maximum duration allowed for streams on the route. If not specified, the value + * from the :ref:`max_stream_duration + * ` field in + * :ref:`HttpConnectionManager.common_http_protocol_options + * ` + * is used. If this field is set explicitly to zero, any + * HttpConnectionManager max_stream_duration timeout will be disabled for + * this route. + */ + 'max_stream_duration': (_google_protobuf_Duration__Output | null); + /** + * If present, and the request contains a `grpc-timeout header + * `_, use that value as the + * *max_stream_duration*, but limit the applied timeout to the maximum value specified here. + * If set to 0, the `grpc-timeout` header is used without modification. + */ + 'grpc_timeout_header_max': (_google_protobuf_Duration__Output | null); + /** + * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by + * subtracting the provided duration from the header. This is useful for allowing Envoy to set + * its global timeout to be less than that of the deadline imposed by the calling client, which + * makes it more likely that Envoy will handle the timeout instead of having the call canceled + * by the client. If, after applying the offset, the resulting timeout is zero or negative, + * the stream will timeout immediately. + */ + 'grpc_timeout_header_offset': (_google_protobuf_Duration__Output | null); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter { + /** + * The name of the URL query parameter that will be used to obtain the hash + * key. If the parameter is not present, no hash will be produced. Query + * parameter names are case-sensitive. + */ + 'name'?: (string); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter__Output { + /** + * The name of the URL query parameter that will be used to obtain the hash + * key. If the parameter is not present, no hash will be produced. Query + * parameter names are case-sensitive. + */ + 'name': (string); +} + +/** + * The router is capable of shadowing traffic from one cluster to another. The current + * implementation is "fire and forget," meaning Envoy will not wait for the shadow cluster to + * respond before returning the response from the primary cluster. All normal statistics are + * collected for the shadow cluster making this feature useful for testing. + * + * During shadowing, the host/authority header is altered such that *-shadow* is appended. This is + * useful for logging. For example, *cluster1* becomes *cluster1-shadow*. + * + * .. note:: + * + * Shadowing will not be triggered if the primary cluster does not exist. + */ +export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy { + /** + * Specifies the cluster that requests will be mirrored to. The cluster must + * exist in the cluster manager configuration. + */ + 'cluster'?: (string); + /** + * If not specified, all requests to the target cluster will be mirrored. + * + * If specified, this field takes precedence over the `runtime_key` field and requests must also + * fall under the percentage of matches indicated by this field. + * + * For some fraction N/D, a random number in the range [0,D) is selected. If the + * number is <= the value of the numerator N, or if the key is not present, the default + * value, the request will be mirrored. + */ + 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); + /** + * Determines if the trace span should be sampled. Defaults to true. + */ + 'trace_sampled'?: (_google_protobuf_BoolValue | null); +} + +/** + * The router is capable of shadowing traffic from one cluster to another. The current + * implementation is "fire and forget," meaning Envoy will not wait for the shadow cluster to + * respond before returning the response from the primary cluster. All normal statistics are + * collected for the shadow cluster making this feature useful for testing. + * + * During shadowing, the host/authority header is altered such that *-shadow* is appended. This is + * useful for logging. For example, *cluster1* becomes *cluster1-shadow*. + * + * .. note:: + * + * Shadowing will not be triggered if the primary cluster does not exist. + */ +export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output { + /** + * Specifies the cluster that requests will be mirrored to. The cluster must + * exist in the cluster manager configuration. + */ + 'cluster': (string); + /** + * If not specified, all requests to the target cluster will be mirrored. + * + * If specified, this field takes precedence over the `runtime_key` field and requests must also + * fall under the percentage of matches indicated by this field. + * + * For some fraction N/D, a random number in the range [0,D) is selected. If the + * number is <= the value of the numerator N, or if the key is not present, the default + * value, the request will be mirrored. + */ + 'runtime_fraction': (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); + /** + * Determines if the trace span should be sampled. Defaults to true. + */ + 'trace_sampled': (_google_protobuf_BoolValue__Output | null); +} + +/** + * Allows enabling and disabling upgrades on a per-route basis. + * This overrides any enabled/disabled upgrade filter chain specified in the + * HttpConnectionManager + * :ref:`upgrade_configs + * ` + * but does not affect any custom filter chain specified there. + */ +export interface _envoy_config_route_v3_RouteAction_UpgradeConfig { + /** + * The case-insensitive name of this upgrade, e.g. "websocket". + * For each upgrade type present in upgrade_configs, requests with + * Upgrade: [upgrade_type] will be proxied upstream. + */ + 'upgrade_type'?: (string); + /** + * Determines if upgrades are available on this route. Defaults to true. + */ + 'enabled'?: (_google_protobuf_BoolValue | null); + /** + * Configuration for sending data upstream as a raw data payload. This is used for + * CONNECT requests, when forwarding CONNECT payload as raw TCP. + * Note that CONNECT support is currently considered alpha in Envoy. + * [#comment: TODO(htuch): Replace the above comment with an alpha tag.] + */ + 'connect_config'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig | null); +} + +/** + * Allows enabling and disabling upgrades on a per-route basis. + * This overrides any enabled/disabled upgrade filter chain specified in the + * HttpConnectionManager + * :ref:`upgrade_configs + * ` + * but does not affect any custom filter chain specified there. + */ +export interface _envoy_config_route_v3_RouteAction_UpgradeConfig__Output { + /** + * The case-insensitive name of this upgrade, e.g. "websocket". + * For each upgrade type present in upgrade_configs, requests with + * Upgrade: [upgrade_type] will be proxied upstream. + */ + 'upgrade_type': (string); + /** + * Determines if upgrades are available on this route. Defaults to true. + */ + 'enabled': (_google_protobuf_BoolValue__Output | null); + /** + * Configuration for sending data upstream as a raw data payload. This is used for + * CONNECT requests, when forwarding CONNECT payload as raw TCP. + * Note that CONNECT support is currently considered alpha in Envoy. + * [#comment: TODO(htuch): Replace the above comment with an alpha tag.] + */ + 'connect_config': (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig__Output | null); +} + +/** + * [#next-free-field: 38] + */ +export interface RouteAction { + /** + * Indicates the upstream cluster to which the request should be routed + * to. + */ + 'cluster'?: (string); + /** + * Envoy will determine the cluster to route to by reading the value of the + * HTTP header named by cluster_header from the request headers. If the + * header is not found or the referenced cluster does not exist, Envoy will + * return a 404 response. + * + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 + * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'cluster_header'?: (string); + /** + * Multiple upstream clusters can be specified for a given route. The + * request is routed to one of the upstream clusters based on weights + * assigned to each cluster. See + * :ref:`traffic splitting ` + * for additional documentation. + */ + 'weighted_clusters'?: (_envoy_config_route_v3_WeightedCluster | null); + /** + * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints + * in the upstream cluster with metadata matching what's set in this field will be considered + * for load balancing. If using :ref:`weighted_clusters + * `, metadata will be merged, with values + * provided there taking precedence. The filter name should be specified as *envoy.lb*. + */ + 'metadata_match'?: (_envoy_config_core_v3_Metadata | null); + /** + * Indicates that during forwarding, the matched prefix (or path) should be + * swapped with this value. This option allows application URLs to be rooted + * at a different path from those exposed at the reverse proxy layer. The router filter will + * place the original path before rewrite into the :ref:`x-envoy-original-path + * ` header. + * + * Only one of *prefix_rewrite* or + * :ref:`regex_rewrite ` + * may be specified. + * + * .. attention:: + * + * Pay careful attention to the use of trailing slashes in the + * :ref:`route's match ` prefix value. + * Stripping a prefix from a path requires multiple Routes to handle all cases. For example, + * rewriting * /prefix* to * /* and * /prefix/etc* to * /etc* cannot be done in a single + * :ref:`Route `, as shown by the below config entries: + * + * .. code-block:: yaml + * + * - match: + * prefix: "/prefix/" + * route: + * prefix_rewrite: "/" + * - match: + * prefix: "/prefix" + * route: + * prefix_rewrite: "/" + * + * Having above entries in the config, requests to * /prefix* will be stripped to * /*, while + * requests to * /prefix/etc* will be stripped to * /etc*. + */ + 'prefix_rewrite'?: (string); + /** + * Indicates that during forwarding, the host header will be swapped with + * this value. + */ + 'host_rewrite_literal'?: (string); + /** + * Indicates that during forwarding, the host header will be swapped with + * the hostname of the upstream host chosen by the cluster manager. This + * option is applicable only when the destination cluster for a route is of + * type *strict_dns* or *logical_dns*. Setting this to true with other cluster + * types has no effect. + */ + 'auto_host_rewrite'?: (_google_protobuf_BoolValue | null); + /** + * Specifies the upstream timeout for the route. If not specified, the default is 15s. This + * spans between the point at which the entire downstream request (i.e. end-of-stream) has been + * processed and when the upstream response has been completely processed. A value of 0 will + * disable the route's timeout. + * + * .. note:: + * + * This timeout includes all retries. See also + * :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, + * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the + * :ref:`retry overview `. + */ + 'timeout'?: (_google_protobuf_Duration | null); + /** + * Indicates that the route has a retry policy. Note that if this is set, + * it'll take precedence over the virtual host level retry policy entirely + * (e.g.: policies are not merged, most internal one becomes the enforced policy). + */ + 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy | null); + /** + * Optionally specifies the :ref:`routing priority `. + */ + 'priority'?: (_envoy_config_core_v3_RoutingPriority | keyof typeof _envoy_config_core_v3_RoutingPriority); + /** + * Specifies a set of rate limit configurations that could be applied to the + * route. + */ + 'rate_limits'?: (_envoy_config_route_v3_RateLimit)[]; + /** + * Specifies if the rate limit filter should include the virtual host rate + * limits. By default, if the route configured rate limits, the virtual host + * :ref:`rate_limits ` are not applied to the + * request. + * + * This field is deprecated. Please use :ref:`vh_rate_limits ` + */ + 'include_vh_rate_limits'?: (_google_protobuf_BoolValue | null); + /** + * Specifies a list of hash policies to use for ring hash load balancing. Each + * hash policy is evaluated individually and the combined result is used to + * route the request. The method of combination is deterministic such that + * identical lists of hash policies will produce the same hash. Since a hash + * policy examines specific parts of a request, it can fail to produce a hash + * (i.e. if the hashed header is not present). If (and only if) all configured + * hash policies fail to generate a hash, no hash will be produced for + * the route. In this case, the behavior is the same as if no hash policies + * were specified (i.e. the ring hash load balancer will choose a random + * backend). If a hash policy has the "terminal" attribute set to true, and + * there is already a hash generated, the hash is returned immediately, + * ignoring the rest of the hash policy list. + */ + 'hash_policy'?: (_envoy_config_route_v3_RouteAction_HashPolicy)[]; + /** + * Indicates that the route has a CORS policy. + */ + 'cors'?: (_envoy_config_route_v3_CorsPolicy | null); + /** + * The HTTP status code to use when configured cluster is not found. + * The default response code is 503 Service Unavailable. + */ + 'cluster_not_found_response_code'?: (_envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode | keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); + /** + * Deprecated by :ref:`grpc_timeout_header_max ` + * If present, and the request is a gRPC request, use the + * `grpc-timeout header `_, + * or its default value (infinity) instead of + * :ref:`timeout `, but limit the applied timeout + * to the maximum value specified here. If configured as 0, the maximum allowed timeout for + * gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used + * and gRPC requests time out like any other requests using + * :ref:`timeout ` or its default. + * This can be used to prevent unexpected upstream request timeouts due to potentially long + * time gaps between gRPC request and response in gRPC streaming mode. + * + * .. note:: + * + * If a timeout is specified using :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, it takes + * precedence over `grpc-timeout header `_, when + * both are present. See also + * :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, + * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the + * :ref:`retry overview `. + */ + 'max_grpc_timeout'?: (_google_protobuf_Duration | null); + /** + * Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, + * although the connection manager wide :ref:`stream_idle_timeout + * ` + * will still apply. A value of 0 will completely disable the route's idle timeout, even if a + * connection manager stream idle timeout is configured. + * + * The idle timeout is distinct to :ref:`timeout + * `, which provides an upper bound + * on the upstream response time; :ref:`idle_timeout + * ` instead bounds the amount + * of time the request's stream may be idle. + * + * After header decoding, the idle timeout will apply on downstream and + * upstream request events. Each time an encode/decode event for headers or + * data is processed for the stream, the timer will be reset. If the timeout + * fires, the stream is terminated with a 408 Request Timeout error code if no + * upstream response header has been received, otherwise a stream reset + * occurs. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled according to the value for + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + */ + 'idle_timeout'?: (_google_protobuf_Duration | null); + 'upgrade_configs'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig)[]; + 'internal_redirect_action'?: (_envoy_config_route_v3_RouteAction_InternalRedirectAction | keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction); + /** + * Indicates that the route has a hedge policy. Note that if this is set, + * it'll take precedence over the virtual host level hedge policy entirely + * (e.g.: policies are not merged, most internal one becomes the enforced policy). + */ + 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy | null); + /** + * Deprecated by :ref:`grpc_timeout_header_offset `. + * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting + * the provided duration from the header. This is useful in allowing Envoy to set its global + * timeout to be less than that of the deadline imposed by the calling client, which makes it more + * likely that Envoy will handle the timeout instead of having the call canceled by the client. + * The offset will only be applied if the provided grpc_timeout is greater than the offset. This + * ensures that the offset will only ever decrease the timeout and never set it to 0 (meaning + * infinity). + */ + 'grpc_timeout_offset'?: (_google_protobuf_Duration | null); + /** + * Indicates that during forwarding, the host header will be swapped with the content of given + * downstream or :ref:`custom ` header. + * If header value is empty, host header is left intact. + * + * .. attention:: + * + * Pay attention to the potential security implications of using this option. Provided header + * must come from trusted source. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'host_rewrite_header'?: (string); + /** + * Indicates that the route has request mirroring policies. + */ + 'request_mirror_policies'?: (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy)[]; + /** + * An internal redirect is handled, iff the number of previous internal redirects that a + * downstream request has encountered is lower than this value, and + * :ref:`internal_redirect_action ` + * is set to :ref:`HANDLE_INTERNAL_REDIRECT + * ` + * In the case where a downstream request is bounced among multiple routes by internal redirect, + * the first route that hits this threshold, or has + * :ref:`internal_redirect_action ` + * set to + * :ref:`PASS_THROUGH_INTERNAL_REDIRECT + * ` + * will pass the redirect back to downstream. + * + * If not specified, at most one redirect will be followed. + */ + 'max_internal_redirects'?: (_google_protobuf_UInt32Value | null); + /** + * Indicates that during forwarding, portions of the path that match the + * pattern should be rewritten, even allowing the substitution of capture + * groups from the pattern into the new path as specified by the rewrite + * substitution string. This is useful to allow application paths to be + * rewritten in a way that is aware of segments with variable content like + * identifiers. The router filter will place the original path as it was + * before the rewrite into the :ref:`x-envoy-original-path + * ` header. + * + * Only one of :ref:`prefix_rewrite ` + * or *regex_rewrite* may be specified. + * + * Examples using Google's `RE2 `_ engine: + * + * * The path pattern ``^/service/([^/]+)(/.*)$`` paired with a substitution + * string of ``\2/instance/\1`` would transform ``/service/foo/v1/api`` + * into ``/v1/api/instance/foo``. + * + * * The pattern ``one`` paired with a substitution string of ``two`` would + * transform ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/two/zzz``. + * + * * The pattern ``^(.*?)one(.*)$`` paired with a substitution string of + * ``\1two\2`` would replace only the first occurrence of ``one``, + * transforming path ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/one/zzz``. + * + * * The pattern ``(?i)/xxx/`` paired with a substitution string of ``/yyy/`` + * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to + * ``/aaa/yyy/bbb``. + */ + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); + /** + * [#not-implemented-hide:] + * Specifies the configuration for retry policy extension. Note that if this is set, it'll take + * precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, + * most internal one becomes the enforced policy). :ref:`Retry policy ` + * should not be set if this field is used. + */ + 'retry_policy_typed_config'?: (_google_protobuf_Any | null); + /** + * If present, Envoy will try to follow an upstream redirect response instead of proxying the + * response back to the downstream. An upstream redirect response is defined + * by :ref:`redirect_response_codes + * `. + */ + 'internal_redirect_policy'?: (_envoy_config_route_v3_InternalRedirectPolicy | null); + /** + * Indicates that during forwarding, the host header will be swapped with + * the result of the regex substitution executed on path value with query and fragment removed. + * This is useful for transitioning variable content between path segment and subdomain. + * + * For example with the following config: + * + * .. code-block:: yaml + * + * host_rewrite_path_regex: + * pattern: + * google_re2: {} + * regex: "^/(.+)/.+$" + * substitution: \1 + * + * Would rewrite the host header to `envoyproxy.io` given the path `/envoyproxy.io/some/path`. + */ + 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); + /** + * Specifies the maximum stream duration for this route. + */ + 'max_stream_duration'?: (_envoy_config_route_v3_RouteAction_MaxStreamDuration | null); + /** + * [#not-implemented-hide:] + * Name of the cluster specifier plugin to use to determine the cluster for + * requests on this route. The plugin name must be defined in the associated + * :ref:`envoy_v3_api_field_config.route.v3.RouteConfiguration.cluster_specifier_plugins` + * in the + * :ref:`envoy_v3_api_field_config.core.v3.TypedExtensionConfig.name` field. + */ + 'cluster_specifier_plugin'?: (string); + 'cluster_specifier'?: "cluster"|"cluster_header"|"weighted_clusters"|"cluster_specifier_plugin"; + 'host_rewrite_specifier'?: "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; +} + +/** + * [#next-free-field: 38] + */ +export interface RouteAction__Output { + /** + * Indicates the upstream cluster to which the request should be routed + * to. + */ + 'cluster'?: (string); + /** + * Envoy will determine the cluster to route to by reading the value of the + * HTTP header named by cluster_header from the request headers. If the + * header is not found or the referenced cluster does not exist, Envoy will + * return a 404 response. + * + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 + * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'cluster_header'?: (string); + /** + * Multiple upstream clusters can be specified for a given route. The + * request is routed to one of the upstream clusters based on weights + * assigned to each cluster. See + * :ref:`traffic splitting ` + * for additional documentation. + */ + 'weighted_clusters'?: (_envoy_config_route_v3_WeightedCluster__Output | null); + /** + * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints + * in the upstream cluster with metadata matching what's set in this field will be considered + * for load balancing. If using :ref:`weighted_clusters + * `, metadata will be merged, with values + * provided there taking precedence. The filter name should be specified as *envoy.lb*. + */ + 'metadata_match': (_envoy_config_core_v3_Metadata__Output | null); + /** + * Indicates that during forwarding, the matched prefix (or path) should be + * swapped with this value. This option allows application URLs to be rooted + * at a different path from those exposed at the reverse proxy layer. The router filter will + * place the original path before rewrite into the :ref:`x-envoy-original-path + * ` header. + * + * Only one of *prefix_rewrite* or + * :ref:`regex_rewrite ` + * may be specified. + * + * .. attention:: + * + * Pay careful attention to the use of trailing slashes in the + * :ref:`route's match ` prefix value. + * Stripping a prefix from a path requires multiple Routes to handle all cases. For example, + * rewriting * /prefix* to * /* and * /prefix/etc* to * /etc* cannot be done in a single + * :ref:`Route `, as shown by the below config entries: + * + * .. code-block:: yaml + * + * - match: + * prefix: "/prefix/" + * route: + * prefix_rewrite: "/" + * - match: + * prefix: "/prefix" + * route: + * prefix_rewrite: "/" + * + * Having above entries in the config, requests to * /prefix* will be stripped to * /*, while + * requests to * /prefix/etc* will be stripped to * /etc*. + */ + 'prefix_rewrite': (string); + /** + * Indicates that during forwarding, the host header will be swapped with + * this value. + */ + 'host_rewrite_literal'?: (string); + /** + * Indicates that during forwarding, the host header will be swapped with + * the hostname of the upstream host chosen by the cluster manager. This + * option is applicable only when the destination cluster for a route is of + * type *strict_dns* or *logical_dns*. Setting this to true with other cluster + * types has no effect. + */ + 'auto_host_rewrite'?: (_google_protobuf_BoolValue__Output | null); + /** + * Specifies the upstream timeout for the route. If not specified, the default is 15s. This + * spans between the point at which the entire downstream request (i.e. end-of-stream) has been + * processed and when the upstream response has been completely processed. A value of 0 will + * disable the route's timeout. + * + * .. note:: + * + * This timeout includes all retries. See also + * :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, + * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the + * :ref:`retry overview `. + */ + 'timeout': (_google_protobuf_Duration__Output | null); + /** + * Indicates that the route has a retry policy. Note that if this is set, + * it'll take precedence over the virtual host level retry policy entirely + * (e.g.: policies are not merged, most internal one becomes the enforced policy). + */ + 'retry_policy': (_envoy_config_route_v3_RetryPolicy__Output | null); + /** + * Optionally specifies the :ref:`routing priority `. + */ + 'priority': (keyof typeof _envoy_config_core_v3_RoutingPriority); + /** + * Specifies a set of rate limit configurations that could be applied to the + * route. + */ + 'rate_limits': (_envoy_config_route_v3_RateLimit__Output)[]; + /** + * Specifies if the rate limit filter should include the virtual host rate + * limits. By default, if the route configured rate limits, the virtual host + * :ref:`rate_limits ` are not applied to the + * request. + * + * This field is deprecated. Please use :ref:`vh_rate_limits ` + */ + 'include_vh_rate_limits': (_google_protobuf_BoolValue__Output | null); + /** + * Specifies a list of hash policies to use for ring hash load balancing. Each + * hash policy is evaluated individually and the combined result is used to + * route the request. The method of combination is deterministic such that + * identical lists of hash policies will produce the same hash. Since a hash + * policy examines specific parts of a request, it can fail to produce a hash + * (i.e. if the hashed header is not present). If (and only if) all configured + * hash policies fail to generate a hash, no hash will be produced for + * the route. In this case, the behavior is the same as if no hash policies + * were specified (i.e. the ring hash load balancer will choose a random + * backend). If a hash policy has the "terminal" attribute set to true, and + * there is already a hash generated, the hash is returned immediately, + * ignoring the rest of the hash policy list. + */ + 'hash_policy': (_envoy_config_route_v3_RouteAction_HashPolicy__Output)[]; + /** + * Indicates that the route has a CORS policy. + */ + 'cors': (_envoy_config_route_v3_CorsPolicy__Output | null); + /** + * The HTTP status code to use when configured cluster is not found. + * The default response code is 503 Service Unavailable. + */ + 'cluster_not_found_response_code': (keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); + /** + * Deprecated by :ref:`grpc_timeout_header_max ` + * If present, and the request is a gRPC request, use the + * `grpc-timeout header `_, + * or its default value (infinity) instead of + * :ref:`timeout `, but limit the applied timeout + * to the maximum value specified here. If configured as 0, the maximum allowed timeout for + * gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used + * and gRPC requests time out like any other requests using + * :ref:`timeout ` or its default. + * This can be used to prevent unexpected upstream request timeouts due to potentially long + * time gaps between gRPC request and response in gRPC streaming mode. + * + * .. note:: + * + * If a timeout is specified using :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, it takes + * precedence over `grpc-timeout header `_, when + * both are present. See also + * :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, + * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the + * :ref:`retry overview `. + */ + 'max_grpc_timeout': (_google_protobuf_Duration__Output | null); + /** + * Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, + * although the connection manager wide :ref:`stream_idle_timeout + * ` + * will still apply. A value of 0 will completely disable the route's idle timeout, even if a + * connection manager stream idle timeout is configured. + * + * The idle timeout is distinct to :ref:`timeout + * `, which provides an upper bound + * on the upstream response time; :ref:`idle_timeout + * ` instead bounds the amount + * of time the request's stream may be idle. + * + * After header decoding, the idle timeout will apply on downstream and + * upstream request events. Each time an encode/decode event for headers or + * data is processed for the stream, the timer will be reset. If the timeout + * fires, the stream is terminated with a 408 Request Timeout error code if no + * upstream response header has been received, otherwise a stream reset + * occurs. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled according to the value for + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + */ + 'idle_timeout': (_google_protobuf_Duration__Output | null); + 'upgrade_configs': (_envoy_config_route_v3_RouteAction_UpgradeConfig__Output)[]; + 'internal_redirect_action': (keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction); + /** + * Indicates that the route has a hedge policy. Note that if this is set, + * it'll take precedence over the virtual host level hedge policy entirely + * (e.g.: policies are not merged, most internal one becomes the enforced policy). + */ + 'hedge_policy': (_envoy_config_route_v3_HedgePolicy__Output | null); + /** + * Deprecated by :ref:`grpc_timeout_header_offset `. + * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting + * the provided duration from the header. This is useful in allowing Envoy to set its global + * timeout to be less than that of the deadline imposed by the calling client, which makes it more + * likely that Envoy will handle the timeout instead of having the call canceled by the client. + * The offset will only be applied if the provided grpc_timeout is greater than the offset. This + * ensures that the offset will only ever decrease the timeout and never set it to 0 (meaning + * infinity). + */ + 'grpc_timeout_offset': (_google_protobuf_Duration__Output | null); + /** + * Indicates that during forwarding, the host header will be swapped with the content of given + * downstream or :ref:`custom ` header. + * If header value is empty, host header is left intact. + * + * .. attention:: + * + * Pay attention to the potential security implications of using this option. Provided header + * must come from trusted source. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'host_rewrite_header'?: (string); + /** + * Indicates that the route has request mirroring policies. + */ + 'request_mirror_policies': (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output)[]; + /** + * An internal redirect is handled, iff the number of previous internal redirects that a + * downstream request has encountered is lower than this value, and + * :ref:`internal_redirect_action ` + * is set to :ref:`HANDLE_INTERNAL_REDIRECT + * ` + * In the case where a downstream request is bounced among multiple routes by internal redirect, + * the first route that hits this threshold, or has + * :ref:`internal_redirect_action ` + * set to + * :ref:`PASS_THROUGH_INTERNAL_REDIRECT + * ` + * will pass the redirect back to downstream. + * + * If not specified, at most one redirect will be followed. + */ + 'max_internal_redirects': (_google_protobuf_UInt32Value__Output | null); + /** + * Indicates that during forwarding, portions of the path that match the + * pattern should be rewritten, even allowing the substitution of capture + * groups from the pattern into the new path as specified by the rewrite + * substitution string. This is useful to allow application paths to be + * rewritten in a way that is aware of segments with variable content like + * identifiers. The router filter will place the original path as it was + * before the rewrite into the :ref:`x-envoy-original-path + * ` header. + * + * Only one of :ref:`prefix_rewrite ` + * or *regex_rewrite* may be specified. + * + * Examples using Google's `RE2 `_ engine: + * + * * The path pattern ``^/service/([^/]+)(/.*)$`` paired with a substitution + * string of ``\2/instance/\1`` would transform ``/service/foo/v1/api`` + * into ``/v1/api/instance/foo``. + * + * * The pattern ``one`` paired with a substitution string of ``two`` would + * transform ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/two/zzz``. + * + * * The pattern ``^(.*?)one(.*)$`` paired with a substitution string of + * ``\1two\2`` would replace only the first occurrence of ``one``, + * transforming path ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/one/zzz``. + * + * * The pattern ``(?i)/xxx/`` paired with a substitution string of ``/yyy/`` + * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to + * ``/aaa/yyy/bbb``. + */ + 'regex_rewrite': (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); + /** + * [#not-implemented-hide:] + * Specifies the configuration for retry policy extension. Note that if this is set, it'll take + * precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, + * most internal one becomes the enforced policy). :ref:`Retry policy ` + * should not be set if this field is used. + */ + 'retry_policy_typed_config': (_google_protobuf_Any__Output | null); + /** + * If present, Envoy will try to follow an upstream redirect response instead of proxying the + * response back to the downstream. An upstream redirect response is defined + * by :ref:`redirect_response_codes + * `. + */ + 'internal_redirect_policy': (_envoy_config_route_v3_InternalRedirectPolicy__Output | null); + /** + * Indicates that during forwarding, the host header will be swapped with + * the result of the regex substitution executed on path value with query and fragment removed. + * This is useful for transitioning variable content between path segment and subdomain. + * + * For example with the following config: + * + * .. code-block:: yaml + * + * host_rewrite_path_regex: + * pattern: + * google_re2: {} + * regex: "^/(.+)/.+$" + * substitution: \1 + * + * Would rewrite the host header to `envoyproxy.io` given the path `/envoyproxy.io/some/path`. + */ + 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); + /** + * Specifies the maximum stream duration for this route. + */ + 'max_stream_duration': (_envoy_config_route_v3_RouteAction_MaxStreamDuration__Output | null); + /** + * [#not-implemented-hide:] + * Name of the cluster specifier plugin to use to determine the cluster for + * requests on this route. The plugin name must be defined in the associated + * :ref:`envoy_v3_api_field_config.route.v3.RouteConfiguration.cluster_specifier_plugins` + * in the + * :ref:`envoy_v3_api_field_config.core.v3.TypedExtensionConfig.name` field. + */ + 'cluster_specifier_plugin'?: (string); + 'cluster_specifier': "cluster"|"cluster_header"|"weighted_clusters"|"cluster_specifier_plugin"; + 'host_rewrite_specifier': "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts new file mode 100644 index 000000000..516f4b06b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts @@ -0,0 +1,228 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route.proto + +import type { VirtualHost as _envoy_config_route_v3_VirtualHost, VirtualHost__Output as _envoy_config_route_v3_VirtualHost__Output } from '../../../../envoy/config/route/v3/VirtualHost'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { Vhds as _envoy_config_route_v3_Vhds, Vhds__Output as _envoy_config_route_v3_Vhds__Output } from '../../../../envoy/config/route/v3/Vhds'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { ClusterSpecifierPlugin as _envoy_config_route_v3_ClusterSpecifierPlugin, ClusterSpecifierPlugin__Output as _envoy_config_route_v3_ClusterSpecifierPlugin__Output } from '../../../../envoy/config/route/v3/ClusterSpecifierPlugin'; + +/** + * [#next-free-field: 13] + */ +export interface RouteConfiguration { + /** + * The name of the route configuration. For example, it might match + * :ref:`route_config_name + * ` in + * :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. + */ + 'name'?: (string); + /** + * An array of virtual hosts that make up the route table. + */ + 'virtual_hosts'?: (_envoy_config_route_v3_VirtualHost)[]; + /** + * Optionally specifies a list of HTTP headers that the connection manager + * will consider to be internal only. If they are found on external requests they will be cleaned + * prior to filter invocation. See :ref:`config_http_conn_man_headers_x-envoy-internal` for more + * information. + */ + 'internal_only_headers'?: (string)[]; + /** + * Specifies a list of HTTP headers that should be added to each response that + * the connection manager encodes. Headers specified at this level are applied + * after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * Specifies a list of HTTP headers that should be removed from each response + * that the connection manager encodes. + */ + 'response_headers_to_remove'?: (string)[]; + /** + * Specifies a list of HTTP headers that should be added to each request + * routed by the HTTP connection manager. Headers specified at this level are + * applied after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * An optional boolean that specifies whether the clusters that the route + * table refers to will be validated by the cluster manager. If set to true + * and a route refers to a non-existent cluster, the route table will not + * load. If set to false and a route refers to a non-existent cluster, the + * route table will load and the router filter will return a 404 if the route + * is selected at runtime. This setting defaults to true if the route table + * is statically defined via the :ref:`route_config + * ` + * option. This setting default to false if the route table is loaded dynamically via the + * :ref:`rds + * ` + * option. Users may wish to override the default behavior in certain cases (for example when + * using CDS with a static route table). + */ + 'validate_clusters'?: (_google_protobuf_BoolValue | null); + /** + * Specifies a list of HTTP headers that should be removed from each request + * routed by the HTTP connection manager. + */ + 'request_headers_to_remove'?: (string)[]; + /** + * An array of virtual hosts will be dynamically loaded via the VHDS API. + * Both *virtual_hosts* and *vhds* fields will be used when present. *virtual_hosts* can be used + * for a base routing table or for infrequently changing virtual hosts. *vhds* is used for + * on-demand discovery of virtual hosts. The contents of these two fields will be merged to + * generate a routing table for a given RouteConfiguration, with *vhds* derived configuration + * taking precedence. + */ + 'vhds'?: (_envoy_config_route_v3_Vhds | null); + /** + * By default, headers that should be added/removed are evaluated from most to least specific: + * + * * route level + * * virtual host level + * * connection manager level + * + * To allow setting overrides at the route or virtual host level, this order can be reversed + * by setting this option to true. Defaults to false. + * + * [#next-major-version: In the v3 API, this will default to true.] + */ + 'most_specific_header_mutations_wins'?: (boolean); + /** + * The maximum bytes of the response :ref:`direct response body + * ` size. If not specified the default + * is 4096. + * + * .. warning:: + * + * Envoy currently holds the content of :ref:`direct response body + * ` in memory. Be careful setting + * this to be larger than the default 4KB, since the allocated memory for direct response body + * is not subject to data plane buffering controls. + */ + 'max_direct_response_body_size_bytes'?: (_google_protobuf_UInt32Value | null); + /** + * [#not-implemented-hide:] + * A list of plugins and their configurations which may be used by a + * :ref:`envoy_v3_api_field_config.route.v3.RouteAction.cluster_specifier_plugin` + * within the route. All *extension.name* fields in this list must be unique. + */ + 'cluster_specifier_plugins'?: (_envoy_config_route_v3_ClusterSpecifierPlugin)[]; +} + +/** + * [#next-free-field: 13] + */ +export interface RouteConfiguration__Output { + /** + * The name of the route configuration. For example, it might match + * :ref:`route_config_name + * ` in + * :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. + */ + 'name': (string); + /** + * An array of virtual hosts that make up the route table. + */ + 'virtual_hosts': (_envoy_config_route_v3_VirtualHost__Output)[]; + /** + * Optionally specifies a list of HTTP headers that the connection manager + * will consider to be internal only. If they are found on external requests they will be cleaned + * prior to filter invocation. See :ref:`config_http_conn_man_headers_x-envoy-internal` for more + * information. + */ + 'internal_only_headers': (string)[]; + /** + * Specifies a list of HTTP headers that should be added to each response that + * the connection manager encodes. Headers specified at this level are applied + * after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * Specifies a list of HTTP headers that should be removed from each response + * that the connection manager encodes. + */ + 'response_headers_to_remove': (string)[]; + /** + * Specifies a list of HTTP headers that should be added to each request + * routed by the HTTP connection manager. Headers specified at this level are + * applied after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * An optional boolean that specifies whether the clusters that the route + * table refers to will be validated by the cluster manager. If set to true + * and a route refers to a non-existent cluster, the route table will not + * load. If set to false and a route refers to a non-existent cluster, the + * route table will load and the router filter will return a 404 if the route + * is selected at runtime. This setting defaults to true if the route table + * is statically defined via the :ref:`route_config + * ` + * option. This setting default to false if the route table is loaded dynamically via the + * :ref:`rds + * ` + * option. Users may wish to override the default behavior in certain cases (for example when + * using CDS with a static route table). + */ + 'validate_clusters': (_google_protobuf_BoolValue__Output | null); + /** + * Specifies a list of HTTP headers that should be removed from each request + * routed by the HTTP connection manager. + */ + 'request_headers_to_remove': (string)[]; + /** + * An array of virtual hosts will be dynamically loaded via the VHDS API. + * Both *virtual_hosts* and *vhds* fields will be used when present. *virtual_hosts* can be used + * for a base routing table or for infrequently changing virtual hosts. *vhds* is used for + * on-demand discovery of virtual hosts. The contents of these two fields will be merged to + * generate a routing table for a given RouteConfiguration, with *vhds* derived configuration + * taking precedence. + */ + 'vhds': (_envoy_config_route_v3_Vhds__Output | null); + /** + * By default, headers that should be added/removed are evaluated from most to least specific: + * + * * route level + * * virtual host level + * * connection manager level + * + * To allow setting overrides at the route or virtual host level, this order can be reversed + * by setting this option to true. Defaults to false. + * + * [#next-major-version: In the v3 API, this will default to true.] + */ + 'most_specific_header_mutations_wins': (boolean); + /** + * The maximum bytes of the response :ref:`direct response body + * ` size. If not specified the default + * is 4096. + * + * .. warning:: + * + * Envoy currently holds the content of :ref:`direct response body + * ` in memory. Be careful setting + * this to be larger than the default 4KB, since the allocated memory for direct response body + * is not subject to data plane buffering controls. + */ + 'max_direct_response_body_size_bytes': (_google_protobuf_UInt32Value__Output | null); + /** + * [#not-implemented-hide:] + * A list of plugins and their configurations which may be used by a + * :ref:`envoy_v3_api_field_config.route.v3.RouteAction.cluster_specifier_plugin` + * within the route. All *extension.name* fields in this list must be unique. + */ + 'cluster_specifier_plugins': (_envoy_config_route_v3_ClusterSpecifierPlugin__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts new file mode 100644 index 000000000..9d872ed18 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts @@ -0,0 +1,276 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; +import type { QueryParameterMatcher as _envoy_config_route_v3_QueryParameterMatcher, QueryParameterMatcher__Output as _envoy_config_route_v3_QueryParameterMatcher__Output } from '../../../../envoy/config/route/v3/QueryParameterMatcher'; +import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; +import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; +import type { MetadataMatcher as _envoy_type_matcher_v3_MetadataMatcher, MetadataMatcher__Output as _envoy_type_matcher_v3_MetadataMatcher__Output } from '../../../../envoy/type/matcher/v3/MetadataMatcher'; + +/** + * An extensible message for matching CONNECT requests. + */ +export interface _envoy_config_route_v3_RouteMatch_ConnectMatcher { +} + +/** + * An extensible message for matching CONNECT requests. + */ +export interface _envoy_config_route_v3_RouteMatch_ConnectMatcher__Output { +} + +export interface _envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions { +} + +export interface _envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions__Output { +} + +export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions { + /** + * If specified, the route will match against whether or not a certificate is presented. + * If not specified, certificate presentation status (true or false) will not be considered when route matching. + */ + 'presented'?: (_google_protobuf_BoolValue | null); + /** + * If specified, the route will match against whether or not a certificate is validated. + * If not specified, certificate validation status (true or false) will not be considered when route matching. + */ + 'validated'?: (_google_protobuf_BoolValue | null); +} + +export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Output { + /** + * If specified, the route will match against whether or not a certificate is presented. + * If not specified, certificate presentation status (true or false) will not be considered when route matching. + */ + 'presented': (_google_protobuf_BoolValue__Output | null); + /** + * If specified, the route will match against whether or not a certificate is validated. + * If not specified, certificate validation status (true or false) will not be considered when route matching. + */ + 'validated': (_google_protobuf_BoolValue__Output | null); +} + +/** + * [#next-free-field: 14] + */ +export interface RouteMatch { + /** + * If specified, the route is a prefix rule meaning that the prefix must + * match the beginning of the *:path* header. + */ + 'prefix'?: (string); + /** + * If specified, the route is an exact path rule meaning that the path must + * exactly match the *:path* header once the query string is removed. + */ + 'path'?: (string); + /** + * Indicates that prefix/path matching should be case sensitive. The default + * is true. Ignored for safe_regex matching. + */ + 'case_sensitive'?: (_google_protobuf_BoolValue | null); + /** + * Specifies a set of headers that the route should match on. The router will + * check the request’s headers against all the specified headers in the route + * config. A match will happen if all the headers in the route are present in + * the request with the same values (or based on presence if the value field + * is not in the config). + */ + 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; + /** + * Specifies a set of URL query parameters on which the route should + * match. The router will check the query string from the *path* header + * against all the specified query parameters. If the number of specified + * query parameters is nonzero, they all must match the *path* header's + * query string for a match to occur. + * + * .. note:: + * + * If query parameters are used to pass request message fields when + * `grpc_json_transcoder `_ + * is used, the transcoded message fields maybe different. The query parameters are + * url encoded, but the message fields are not. For example, if a query + * parameter is "foo%20bar", the message field will be "foo bar". + */ + 'query_parameters'?: (_envoy_config_route_v3_QueryParameterMatcher)[]; + /** + * If specified, only gRPC requests will be matched. The router will check + * that the content-type header has a application/grpc or one of the various + * application/grpc+ values. + */ + 'grpc'?: (_envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions | null); + /** + * Indicates that the route should additionally match on a runtime key. Every time the route + * is considered for a match, it must also fall under the percentage of matches indicated by + * this field. For some fraction N/D, a random number in the range [0,D) is selected. If the + * number is <= the value of the numerator N, or if the key is not present, the default + * value, the router continues to evaluate the remaining match criteria. A runtime_fraction + * route configuration can be used to roll out route changes in a gradual manner without full + * code/config deploys. Refer to the :ref:`traffic shifting + * ` docs for additional documentation. + * + * .. note:: + * + * Parsing this field is implemented such that the runtime key's data may be represented + * as a FractionalPercent proto represented as JSON/YAML and may also be represented as an + * integer with the assumption that the value is an integral percentage out of 100. For + * instance, a runtime key lookup returning the value "42" would parse as a FractionalPercent + * whose numerator is 42 and denominator is HUNDRED. This preserves legacy semantics. + */ + 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); + /** + * If specified, the route is a regular expression rule meaning that the + * regex must match the *:path* header once the query string is removed. The entire path + * (without the query string) must match the regex. The rule will not match if only a + * subsequence of the *:path* header matches the regex. + * + * [#next-major-version: In the v3 API we should redo how path specification works such + * that we utilize StringMatcher, and additionally have consistent options around whether we + * strip query strings, do a case sensitive match, etc. In the interim it will be too disruptive + * to deprecate the existing options. We should even consider whether we want to do away with + * path_specifier entirely and just rely on a set of header matchers which can already match + * on :path, etc. The issue with that is it is unclear how to generically deal with query string + * stripping. This needs more thought.] + */ + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher | null); + /** + * If specified, the client tls context will be matched against the defined + * match options. + * + * [#next-major-version: unify with RBAC] + */ + 'tls_context'?: (_envoy_config_route_v3_RouteMatch_TlsContextMatchOptions | null); + /** + * If this is used as the matcher, the matcher will only match CONNECT requests. + * Note that this will not match HTTP/2 upgrade-style CONNECT requests + * (WebSocket and the like) as they are normalized in Envoy as HTTP/1.1 style + * upgrades. + * This is the only way to match CONNECT requests for HTTP/1.1. For HTTP/2, + * where Extended CONNECT requests may have a path, the path matchers will work if + * there is a path present. + * Note that CONNECT support is currently considered alpha in Envoy. + * [#comment: TODO(htuch): Replace the above comment with an alpha tag.] + */ + 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher | null); + /** + * Specifies a set of dynamic metadata matchers on which the route should match. + * The router will check the dynamic metadata against all the specified dynamic metadata matchers. + * If the number of specified dynamic metadata matchers is nonzero, they all must match the + * dynamic metadata for a match to occur. + */ + 'dynamic_metadata'?: (_envoy_type_matcher_v3_MetadataMatcher)[]; + 'path_specifier'?: "prefix"|"path"|"safe_regex"|"connect_matcher"; +} + +/** + * [#next-free-field: 14] + */ +export interface RouteMatch__Output { + /** + * If specified, the route is a prefix rule meaning that the prefix must + * match the beginning of the *:path* header. + */ + 'prefix'?: (string); + /** + * If specified, the route is an exact path rule meaning that the path must + * exactly match the *:path* header once the query string is removed. + */ + 'path'?: (string); + /** + * Indicates that prefix/path matching should be case sensitive. The default + * is true. Ignored for safe_regex matching. + */ + 'case_sensitive': (_google_protobuf_BoolValue__Output | null); + /** + * Specifies a set of headers that the route should match on. The router will + * check the request’s headers against all the specified headers in the route + * config. A match will happen if all the headers in the route are present in + * the request with the same values (or based on presence if the value field + * is not in the config). + */ + 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; + /** + * Specifies a set of URL query parameters on which the route should + * match. The router will check the query string from the *path* header + * against all the specified query parameters. If the number of specified + * query parameters is nonzero, they all must match the *path* header's + * query string for a match to occur. + * + * .. note:: + * + * If query parameters are used to pass request message fields when + * `grpc_json_transcoder `_ + * is used, the transcoded message fields maybe different. The query parameters are + * url encoded, but the message fields are not. For example, if a query + * parameter is "foo%20bar", the message field will be "foo bar". + */ + 'query_parameters': (_envoy_config_route_v3_QueryParameterMatcher__Output)[]; + /** + * If specified, only gRPC requests will be matched. The router will check + * that the content-type header has a application/grpc or one of the various + * application/grpc+ values. + */ + 'grpc': (_envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions__Output | null); + /** + * Indicates that the route should additionally match on a runtime key. Every time the route + * is considered for a match, it must also fall under the percentage of matches indicated by + * this field. For some fraction N/D, a random number in the range [0,D) is selected. If the + * number is <= the value of the numerator N, or if the key is not present, the default + * value, the router continues to evaluate the remaining match criteria. A runtime_fraction + * route configuration can be used to roll out route changes in a gradual manner without full + * code/config deploys. Refer to the :ref:`traffic shifting + * ` docs for additional documentation. + * + * .. note:: + * + * Parsing this field is implemented such that the runtime key's data may be represented + * as a FractionalPercent proto represented as JSON/YAML and may also be represented as an + * integer with the assumption that the value is an integral percentage out of 100. For + * instance, a runtime key lookup returning the value "42" would parse as a FractionalPercent + * whose numerator is 42 and denominator is HUNDRED. This preserves legacy semantics. + */ + 'runtime_fraction': (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); + /** + * If specified, the route is a regular expression rule meaning that the + * regex must match the *:path* header once the query string is removed. The entire path + * (without the query string) must match the regex. The rule will not match if only a + * subsequence of the *:path* header matches the regex. + * + * [#next-major-version: In the v3 API we should redo how path specification works such + * that we utilize StringMatcher, and additionally have consistent options around whether we + * strip query strings, do a case sensitive match, etc. In the interim it will be too disruptive + * to deprecate the existing options. We should even consider whether we want to do away with + * path_specifier entirely and just rely on a set of header matchers which can already match + * on :path, etc. The issue with that is it is unclear how to generically deal with query string + * stripping. This needs more thought.] + */ + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); + /** + * If specified, the client tls context will be matched against the defined + * match options. + * + * [#next-major-version: unify with RBAC] + */ + 'tls_context': (_envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Output | null); + /** + * If this is used as the matcher, the matcher will only match CONNECT requests. + * Note that this will not match HTTP/2 upgrade-style CONNECT requests + * (WebSocket and the like) as they are normalized in Envoy as HTTP/1.1 style + * upgrades. + * This is the only way to match CONNECT requests for HTTP/1.1. For HTTP/2, + * where Extended CONNECT requests may have a path, the path matchers will work if + * there is a path present. + * Note that CONNECT support is currently considered alpha in Envoy. + * [#comment: TODO(htuch): Replace the above comment with an alpha tag.] + */ + 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher__Output | null); + /** + * Specifies a set of dynamic metadata matchers on which the route should match. + * The router will check the dynamic metadata against all the specified dynamic metadata matchers. + * If the number of specified dynamic metadata matchers is nonzero, they all must match the + * dynamic metadata for a match to occur. + */ + 'dynamic_metadata': (_envoy_type_matcher_v3_MetadataMatcher__Output)[]; + 'path_specifier': "prefix"|"path"|"safe_regex"|"connect_matcher"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts new file mode 100644 index 000000000..5865eadd3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts @@ -0,0 +1,212 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/scoped_route.proto + + +export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment { + /** + * A string to match against. + */ + 'string_key'?: (string); + 'type'?: "string_key"; +} + +export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment__Output { + /** + * A string to match against. + */ + 'string_key'?: (string); + 'type': "string_key"; +} + +/** + * Specifies a key which is matched against the output of the + * :ref:`scope_key_builder` + * specified in the HttpConnectionManager. The matching is done per HTTP + * request and is dependent on the order of the fragments contained in the + * Key. + */ +export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key { + /** + * The ordered set of fragments to match against. The order must match the + * fragments in the corresponding + * :ref:`scope_key_builder`. + */ + 'fragments'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment)[]; +} + +/** + * Specifies a key which is matched against the output of the + * :ref:`scope_key_builder` + * specified in the HttpConnectionManager. The matching is done per HTTP + * request and is dependent on the order of the fragments contained in the + * Key. + */ +export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key__Output { + /** + * The ordered set of fragments to match against. The order must match the + * fragments in the corresponding + * :ref:`scope_key_builder`. + */ + 'fragments': (_envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment__Output)[]; +} + +/** + * Specifies a routing scope, which associates a + * :ref:`Key` to a + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). + * + * The HTTP connection manager builds up a table consisting of these Key to + * RouteConfiguration mappings, and looks up the RouteConfiguration to use per + * request according to the algorithm specified in the + * :ref:`scope_key_builder` + * assigned to the HttpConnectionManager. + * + * For example, with the following configurations (in YAML): + * + * HttpConnectionManager config: + * + * .. code:: + * + * ... + * scoped_routes: + * name: foo-scoped-routes + * scope_key_builder: + * fragments: + * - header_value_extractor: + * name: X-Route-Selector + * element_separator: , + * element: + * separator: = + * key: vip + * + * ScopedRouteConfiguration resources (specified statically via + * :ref:`scoped_route_configurations_list` + * or obtained dynamically via SRDS): + * + * .. code:: + * + * (1) + * name: route-scope1 + * route_configuration_name: route-config1 + * key: + * fragments: + * - string_key: 172.10.10.20 + * + * (2) + * name: route-scope2 + * route_configuration_name: route-config2 + * key: + * fragments: + * - string_key: 172.20.20.30 + * + * A request from a client such as: + * + * .. code:: + * + * GET / HTTP/1.1 + * Host: foo.com + * X-Route-Selector: vip=172.10.10.20 + * + * would result in the routing table defined by the `route-config1` + * RouteConfiguration being assigned to the HTTP request/stream. + */ +export interface ScopedRouteConfiguration { + /** + * The name assigned to the routing scope. + */ + 'name'?: (string); + /** + * The resource name to use for a :ref:`envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest` to an + * RDS server to fetch the :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated + * with this scope. + */ + 'route_configuration_name'?: (string); + /** + * The key to match against. + */ + 'key'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key | null); + /** + * Whether the RouteConfiguration should be loaded on demand. + */ + 'on_demand'?: (boolean); +} + +/** + * Specifies a routing scope, which associates a + * :ref:`Key` to a + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). + * + * The HTTP connection manager builds up a table consisting of these Key to + * RouteConfiguration mappings, and looks up the RouteConfiguration to use per + * request according to the algorithm specified in the + * :ref:`scope_key_builder` + * assigned to the HttpConnectionManager. + * + * For example, with the following configurations (in YAML): + * + * HttpConnectionManager config: + * + * .. code:: + * + * ... + * scoped_routes: + * name: foo-scoped-routes + * scope_key_builder: + * fragments: + * - header_value_extractor: + * name: X-Route-Selector + * element_separator: , + * element: + * separator: = + * key: vip + * + * ScopedRouteConfiguration resources (specified statically via + * :ref:`scoped_route_configurations_list` + * or obtained dynamically via SRDS): + * + * .. code:: + * + * (1) + * name: route-scope1 + * route_configuration_name: route-config1 + * key: + * fragments: + * - string_key: 172.10.10.20 + * + * (2) + * name: route-scope2 + * route_configuration_name: route-config2 + * key: + * fragments: + * - string_key: 172.20.20.30 + * + * A request from a client such as: + * + * .. code:: + * + * GET / HTTP/1.1 + * Host: foo.com + * X-Route-Selector: vip=172.10.10.20 + * + * would result in the routing table defined by the `route-config1` + * RouteConfiguration being assigned to the HTTP request/stream. + */ +export interface ScopedRouteConfiguration__Output { + /** + * The name assigned to the routing scope. + */ + 'name': (string); + /** + * The resource name to use for a :ref:`envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest` to an + * RDS server to fetch the :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated + * with this scope. + */ + 'route_configuration_name': (string); + /** + * The key to match against. + */ + 'key': (_envoy_config_route_v3_ScopedRouteConfiguration_Key__Output | null); + /** + * Whether the RouteConfiguration should be loaded on demand. + */ + 'on_demand': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts new file mode 100644 index 000000000..962e9d51a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts @@ -0,0 +1,84 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; +import type { CustomTag as _envoy_type_tracing_v3_CustomTag, CustomTag__Output as _envoy_type_tracing_v3_CustomTag__Output } from '../../../../envoy/type/tracing/v3/CustomTag'; + +export interface Tracing { + /** + * Target percentage of requests managed by this HTTP connection manager that will be force + * traced if the :ref:`x-client-trace-id ` + * header is set. This field is a direct analog for the runtime variable + * 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + * `. + * Default: 100% + */ + 'client_sampling'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Target percentage of requests managed by this HTTP connection manager that will be randomly + * selected for trace generation, if not requested by the client or not forced. This field is + * a direct analog for the runtime variable 'tracing.random_sampling' in the + * :ref:`HTTP Connection Manager `. + * Default: 100% + */ + 'random_sampling'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Target percentage of requests managed by this HTTP connection manager that will be traced + * after all other sampling checks have been applied (client-directed, force tracing, random + * sampling). This field functions as an upper limit on the total configured sampling rate. For + * instance, setting client_sampling to 100% but overall_sampling to 1% will result in only 1% + * of client requests with the appropriate headers to be force traced. This field is a direct + * analog for the runtime variable 'tracing.global_enabled' in the + * :ref:`HTTP Connection Manager `. + * Default: 100% + */ + 'overall_sampling'?: (_envoy_type_v3_FractionalPercent | null); + /** + * A list of custom tags with unique tag name to create tags for the active span. + * It will take effect after merging with the :ref:`corresponding configuration + * ` + * configured in the HTTP connection manager. If two tags with the same name are configured + * each in the HTTP connection manager and the route level, the one configured here takes + * priority. + */ + 'custom_tags'?: (_envoy_type_tracing_v3_CustomTag)[]; +} + +export interface Tracing__Output { + /** + * Target percentage of requests managed by this HTTP connection manager that will be force + * traced if the :ref:`x-client-trace-id ` + * header is set. This field is a direct analog for the runtime variable + * 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + * `. + * Default: 100% + */ + 'client_sampling': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Target percentage of requests managed by this HTTP connection manager that will be randomly + * selected for trace generation, if not requested by the client or not forced. This field is + * a direct analog for the runtime variable 'tracing.random_sampling' in the + * :ref:`HTTP Connection Manager `. + * Default: 100% + */ + 'random_sampling': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Target percentage of requests managed by this HTTP connection manager that will be traced + * after all other sampling checks have been applied (client-directed, force tracing, random + * sampling). This field functions as an upper limit on the total configured sampling rate. For + * instance, setting client_sampling to 100% but overall_sampling to 1% will result in only 1% + * of client requests with the appropriate headers to be force traced. This field is a direct + * analog for the runtime variable 'tracing.global_enabled' in the + * :ref:`HTTP Connection Manager `. + * Default: 100% + */ + 'overall_sampling': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * A list of custom tags with unique tag name to create tags for the active span. + * It will take effect after merging with the :ref:`corresponding configuration + * ` + * configured in the HTTP connection manager. If two tags with the same name are configured + * each in the HTTP connection manager and the route level, the one configured here takes + * priority. + */ + 'custom_tags': (_envoy_type_tracing_v3_CustomTag__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts new file mode 100644 index 000000000..b8a37be65 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; + +export interface Vhds { + /** + * Configuration source specifier for VHDS. + */ + 'config_source'?: (_envoy_config_core_v3_ConfigSource | null); +} + +export interface Vhds__Output { + /** + * Configuration source specifier for VHDS. + */ + 'config_source': (_envoy_config_core_v3_ConfigSource__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts new file mode 100644 index 000000000..7674da733 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts @@ -0,0 +1,71 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; + +/** + * A virtual cluster is a way of specifying a regex matching rule against + * certain important endpoints such that statistics are generated explicitly for + * the matched requests. The reason this is useful is that when doing + * prefix/path matching Envoy does not always know what the application + * considers to be an endpoint. Thus, it’s impossible for Envoy to generically + * emit per endpoint statistics. However, often systems have highly critical + * endpoints that they wish to get “perfect†statistics on. Virtual cluster + * statistics are perfect in the sense that they are emitted on the downstream + * side such that they include network level failures. + * + * Documentation for :ref:`virtual cluster statistics `. + * + * .. note:: + * + * Virtual clusters are a useful tool, but we do not recommend setting up a virtual cluster for + * every application endpoint. This is both not easily maintainable and as well the matching and + * statistics output are not free. + */ +export interface VirtualCluster { + /** + * Specifies the name of the virtual cluster. The virtual cluster name as well + * as the virtual host name are used when emitting statistics. The statistics are emitted by the + * router filter and are documented :ref:`here `. + */ + 'name'?: (string); + /** + * Specifies a list of header matchers to use for matching requests. Each specified header must + * match. The pseudo-headers `:path` and `:method` can be used to match the request path and + * method, respectively. + */ + 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; +} + +/** + * A virtual cluster is a way of specifying a regex matching rule against + * certain important endpoints such that statistics are generated explicitly for + * the matched requests. The reason this is useful is that when doing + * prefix/path matching Envoy does not always know what the application + * considers to be an endpoint. Thus, it’s impossible for Envoy to generically + * emit per endpoint statistics. However, often systems have highly critical + * endpoints that they wish to get “perfect†statistics on. Virtual cluster + * statistics are perfect in the sense that they are emitted on the downstream + * side such that they include network level failures. + * + * Documentation for :ref:`virtual cluster statistics `. + * + * .. note:: + * + * Virtual clusters are a useful tool, but we do not recommend setting up a virtual cluster for + * every application endpoint. This is both not easily maintainable and as well the matching and + * statistics output are not free. + */ +export interface VirtualCluster__Output { + /** + * Specifies the name of the virtual cluster. The virtual cluster name as well + * as the virtual host name are used when emitting statistics. The statistics are emitted by the + * router filter and are documented :ref:`here `. + */ + 'name': (string); + /** + * Specifies a list of header matchers to use for matching requests. Each specified header must + * match. The pseudo-headers `:path` and `:method` can be used to match the request path and + * method, respectively. + */ + 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts new file mode 100644 index 000000000..017900ed9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts @@ -0,0 +1,330 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { Route as _envoy_config_route_v3_Route, Route__Output as _envoy_config_route_v3_Route__Output } from '../../../../envoy/config/route/v3/Route'; +import type { VirtualCluster as _envoy_config_route_v3_VirtualCluster, VirtualCluster__Output as _envoy_config_route_v3_VirtualCluster__Output } from '../../../../envoy/config/route/v3/VirtualCluster'; +import type { RateLimit as _envoy_config_route_v3_RateLimit, RateLimit__Output as _envoy_config_route_v3_RateLimit__Output } from '../../../../envoy/config/route/v3/RateLimit'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; +import type { CorsPolicy as _envoy_config_route_v3_CorsPolicy, CorsPolicy__Output as _envoy_config_route_v3_CorsPolicy__Output } from '../../../../envoy/config/route/v3/CorsPolicy'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { RetryPolicy as _envoy_config_route_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_route_v3_RetryPolicy__Output } from '../../../../envoy/config/route/v3/RetryPolicy'; +import type { HedgePolicy as _envoy_config_route_v3_HedgePolicy, HedgePolicy__Output as _envoy_config_route_v3_HedgePolicy__Output } from '../../../../envoy/config/route/v3/HedgePolicy'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +export enum _envoy_config_route_v3_VirtualHost_TlsRequirementType { + /** + * No TLS requirement for the virtual host. + */ + NONE = 0, + /** + * External requests must use TLS. If a request is external and it is not + * using TLS, a 301 redirect will be sent telling the client to use HTTPS. + */ + EXTERNAL_ONLY = 1, + /** + * All requests must use TLS. If a request is not using TLS, a 301 redirect + * will be sent telling the client to use HTTPS. + */ + ALL = 2, +} + +/** + * The top level element in the routing configuration is a virtual host. Each virtual host has + * a logical name as well as a set of domains that get routed to it based on the incoming request's + * host header. This allows a single listener to service multiple top level domain path trees. Once + * a virtual host is selected based on the domain, the routes are processed in order to see which + * upstream cluster to route to or whether to perform a redirect. + * [#next-free-field: 21] + */ +export interface VirtualHost { + /** + * The logical name of the virtual host. This is used when emitting certain + * statistics but is not relevant for routing. + */ + 'name'?: (string); + /** + * A list of domains (host/authority header) that will be matched to this + * virtual host. Wildcard hosts are supported in the suffix or prefix form. + * + * Domain search order: + * 1. Exact domain names: ``www.foo.com``. + * 2. Suffix domain wildcards: ``*.foo.com`` or ``*-bar.foo.com``. + * 3. Prefix domain wildcards: ``foo.*`` or ``foo-*``. + * 4. Special wildcard ``*`` matching any domain. + * + * .. note:: + * + * The wildcard will not match the empty string. + * e.g. ``*-bar.foo.com`` will match ``baz-bar.foo.com`` but not ``-bar.foo.com``. + * The longest wildcards match first. + * Only a single virtual host in the entire route configuration can match on ``*``. A domain + * must be unique across all virtual hosts or the config will fail to load. + * + * Domains cannot contain control characters. This is validated by the well_known_regex HTTP_HEADER_VALUE. + */ + 'domains'?: (string)[]; + /** + * The list of routes that will be matched, in order, for incoming requests. + * The first route that matches will be used. + */ + 'routes'?: (_envoy_config_route_v3_Route)[]; + /** + * Specifies the type of TLS enforcement the virtual host expects. If this option is not + * specified, there is no TLS requirement for the virtual host. + */ + 'require_tls'?: (_envoy_config_route_v3_VirtualHost_TlsRequirementType | keyof typeof _envoy_config_route_v3_VirtualHost_TlsRequirementType); + /** + * A list of virtual clusters defined for this virtual host. Virtual clusters + * are used for additional statistics gathering. + */ + 'virtual_clusters'?: (_envoy_config_route_v3_VirtualCluster)[]; + /** + * Specifies a set of rate limit configurations that will be applied to the + * virtual host. + */ + 'rate_limits'?: (_envoy_config_route_v3_RateLimit)[]; + /** + * Specifies a list of HTTP headers that should be added to each request + * handled by this virtual host. Headers specified at this level are applied + * after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * details on header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * Indicates that the virtual host has a CORS policy. + */ + 'cors'?: (_envoy_config_route_v3_CorsPolicy | null); + /** + * Specifies a list of HTTP headers that should be added to each response + * handled by this virtual host. Headers specified at this level are applied + * after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * details on header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * Specifies a list of HTTP headers that should be removed from each response + * handled by this virtual host. + */ + 'response_headers_to_remove'?: (string)[]; + /** + * Specifies a list of HTTP headers that should be removed from each request + * handled by this virtual host. + */ + 'request_headers_to_remove'?: (string)[]; + /** + * Decides whether the :ref:`x-envoy-attempt-count + * ` header should be included + * in the upstream request. Setting this option will cause it to override any existing header + * value, so in the case of two Envoys on the request path with this option enabled, the upstream + * will see the attempt count as perceived by the second Envoy. Defaults to false. + * This header is unaffected by the + * :ref:`suppress_envoy_headers + * ` flag. + * + * [#next-major-version: rename to include_attempt_count_in_request.] + */ + 'include_request_attempt_count'?: (boolean); + /** + * The per_filter_config field can be used to provide virtual host-specific + * configurations for filters. The key should match the filter name, such as + * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter + * specific; see the :ref:`HTTP filter documentation ` + * for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] + */ + 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); + /** + * Indicates the retry policy for all routes in this virtual host. Note that setting a + * route level entry will take precedence over this config and it'll be treated + * independently (e.g.: values are not inherited). + */ + 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy | null); + /** + * Indicates the hedge policy for all routes in this virtual host. Note that setting a + * route level entry will take precedence over this config and it'll be treated + * independently (e.g.: values are not inherited). + */ + 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy | null); + /** + * The maximum bytes which will be buffered for retries and shadowing. + * If set and a route-specific limit is not set, the bytes actually buffered will be the minimum + * value of this and the listener per_connection_buffer_limit_bytes. + */ + 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); + /** + * Decides whether the :ref:`x-envoy-attempt-count + * ` header should be included + * in the downstream response. Setting this option will cause the router to override any existing header + * value, so in the case of two Envoys on the request path with this option enabled, the downstream + * will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. + * This header is unaffected by the + * :ref:`suppress_envoy_headers + * ` flag. + */ + 'include_attempt_count_in_response'?: (boolean); + /** + * [#not-implemented-hide:] + * Specifies the configuration for retry policy extension. Note that setting a route level entry + * will take precedence over this config and it'll be treated independently (e.g.: values are not + * inherited). :ref:`Retry policy ` should not be + * set if this field is used. + */ + 'retry_policy_typed_config'?: (_google_protobuf_Any | null); +} + +/** + * The top level element in the routing configuration is a virtual host. Each virtual host has + * a logical name as well as a set of domains that get routed to it based on the incoming request's + * host header. This allows a single listener to service multiple top level domain path trees. Once + * a virtual host is selected based on the domain, the routes are processed in order to see which + * upstream cluster to route to or whether to perform a redirect. + * [#next-free-field: 21] + */ +export interface VirtualHost__Output { + /** + * The logical name of the virtual host. This is used when emitting certain + * statistics but is not relevant for routing. + */ + 'name': (string); + /** + * A list of domains (host/authority header) that will be matched to this + * virtual host. Wildcard hosts are supported in the suffix or prefix form. + * + * Domain search order: + * 1. Exact domain names: ``www.foo.com``. + * 2. Suffix domain wildcards: ``*.foo.com`` or ``*-bar.foo.com``. + * 3. Prefix domain wildcards: ``foo.*`` or ``foo-*``. + * 4. Special wildcard ``*`` matching any domain. + * + * .. note:: + * + * The wildcard will not match the empty string. + * e.g. ``*-bar.foo.com`` will match ``baz-bar.foo.com`` but not ``-bar.foo.com``. + * The longest wildcards match first. + * Only a single virtual host in the entire route configuration can match on ``*``. A domain + * must be unique across all virtual hosts or the config will fail to load. + * + * Domains cannot contain control characters. This is validated by the well_known_regex HTTP_HEADER_VALUE. + */ + 'domains': (string)[]; + /** + * The list of routes that will be matched, in order, for incoming requests. + * The first route that matches will be used. + */ + 'routes': (_envoy_config_route_v3_Route__Output)[]; + /** + * Specifies the type of TLS enforcement the virtual host expects. If this option is not + * specified, there is no TLS requirement for the virtual host. + */ + 'require_tls': (keyof typeof _envoy_config_route_v3_VirtualHost_TlsRequirementType); + /** + * A list of virtual clusters defined for this virtual host. Virtual clusters + * are used for additional statistics gathering. + */ + 'virtual_clusters': (_envoy_config_route_v3_VirtualCluster__Output)[]; + /** + * Specifies a set of rate limit configurations that will be applied to the + * virtual host. + */ + 'rate_limits': (_envoy_config_route_v3_RateLimit__Output)[]; + /** + * Specifies a list of HTTP headers that should be added to each request + * handled by this virtual host. Headers specified at this level are applied + * after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * details on header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * Indicates that the virtual host has a CORS policy. + */ + 'cors': (_envoy_config_route_v3_CorsPolicy__Output | null); + /** + * Specifies a list of HTTP headers that should be added to each response + * handled by this virtual host. Headers specified at this level are applied + * after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * details on header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * Specifies a list of HTTP headers that should be removed from each response + * handled by this virtual host. + */ + 'response_headers_to_remove': (string)[]; + /** + * Specifies a list of HTTP headers that should be removed from each request + * handled by this virtual host. + */ + 'request_headers_to_remove': (string)[]; + /** + * Decides whether the :ref:`x-envoy-attempt-count + * ` header should be included + * in the upstream request. Setting this option will cause it to override any existing header + * value, so in the case of two Envoys on the request path with this option enabled, the upstream + * will see the attempt count as perceived by the second Envoy. Defaults to false. + * This header is unaffected by the + * :ref:`suppress_envoy_headers + * ` flag. + * + * [#next-major-version: rename to include_attempt_count_in_request.] + */ + 'include_request_attempt_count': (boolean); + /** + * The per_filter_config field can be used to provide virtual host-specific + * configurations for filters. The key should match the filter name, such as + * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter + * specific; see the :ref:`HTTP filter documentation ` + * for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] + */ + 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); + /** + * Indicates the retry policy for all routes in this virtual host. Note that setting a + * route level entry will take precedence over this config and it'll be treated + * independently (e.g.: values are not inherited). + */ + 'retry_policy': (_envoy_config_route_v3_RetryPolicy__Output | null); + /** + * Indicates the hedge policy for all routes in this virtual host. Note that setting a + * route level entry will take precedence over this config and it'll be treated + * independently (e.g.: values are not inherited). + */ + 'hedge_policy': (_envoy_config_route_v3_HedgePolicy__Output | null); + /** + * The maximum bytes which will be buffered for retries and shadowing. + * If set and a route-specific limit is not set, the bytes actually buffered will be the minimum + * value of this and the listener per_connection_buffer_limit_bytes. + */ + 'per_request_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); + /** + * Decides whether the :ref:`x-envoy-attempt-count + * ` header should be included + * in the downstream response. Setting this option will cause the router to override any existing header + * value, so in the case of two Envoys on the request path with this option enabled, the downstream + * will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. + * This header is unaffected by the + * :ref:`suppress_envoy_headers + * ` flag. + */ + 'include_attempt_count_in_response': (boolean); + /** + * [#not-implemented-hide:] + * Specifies the configuration for retry policy extension. Note that setting a route level entry + * will take precedence over this config and it'll be treated independently (e.g.: values are not + * inherited). :ref:`Retry policy ` should not be + * set if this field is used. + */ + 'retry_policy_typed_config': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts new file mode 100644 index 000000000..e734073be --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts @@ -0,0 +1,254 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * [#next-free-field: 13] + */ +export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { + /** + * Only one of *name* and *cluster_header* may be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1}] + * Name of the upstream cluster. The cluster must exist in the + * :ref:`cluster manager configuration `. + */ + 'name'?: (string); + /** + * Only one of *name* and *cluster_header* may be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1 }] + * Envoy will determine the cluster to route to by reading the value of the + * HTTP header named by cluster_header from the request headers. If the + * header is not found or the referenced cluster does not exist, Envoy will + * return a 404 response. + * + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 + * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'cluster_header'?: (string); + /** + * An integer between 0 and :ref:`total_weight + * `. When a request matches the route, + * the choice of an upstream cluster is determined by its weight. The sum of weights across all + * entries in the clusters array must add up to the total_weight, which defaults to 100. + */ + 'weight'?: (_google_protobuf_UInt32Value | null); + /** + * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in + * the upstream cluster with metadata matching what is set in this field will be considered for + * load balancing. Note that this will be merged with what's provided in + * :ref:`RouteAction.metadata_match `, with + * values here taking precedence. The filter name should be specified as *envoy.lb*. + */ + 'metadata_match'?: (_envoy_config_core_v3_Metadata | null); + /** + * Specifies a list of headers to be added to requests when this cluster is selected + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. + * Headers specified at this level are applied before headers from the enclosing + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * Specifies a list of HTTP headers that should be removed from each request when + * this cluster is selected through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. + */ + 'request_headers_to_remove'?: (string)[]; + /** + * Specifies a list of headers to be added to responses when this cluster is selected + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. + * Headers specified at this level are applied before headers from the enclosing + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; + /** + * Specifies a list of headers to be removed from responses when this cluster is selected + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. + */ + 'response_headers_to_remove'?: (string)[]; + /** + * The per_filter_config field can be used to provide weighted cluster-specific + * configurations for filters. The key should match the filter name, such as + * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter + * specific; see the :ref:`HTTP filter documentation ` + * for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] + */ + 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); + /** + * Indicates that during forwarding, the host header will be swapped with + * this value. + */ + 'host_rewrite_literal'?: (string); + 'host_rewrite_specifier'?: "host_rewrite_literal"; +} + +/** + * [#next-free-field: 13] + */ +export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { + /** + * Only one of *name* and *cluster_header* may be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1}] + * Name of the upstream cluster. The cluster must exist in the + * :ref:`cluster manager configuration `. + */ + 'name': (string); + /** + * Only one of *name* and *cluster_header* may be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1 }] + * Envoy will determine the cluster to route to by reading the value of the + * HTTP header named by cluster_header from the request headers. If the + * header is not found or the referenced cluster does not exist, Envoy will + * return a 404 response. + * + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 + * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'cluster_header': (string); + /** + * An integer between 0 and :ref:`total_weight + * `. When a request matches the route, + * the choice of an upstream cluster is determined by its weight. The sum of weights across all + * entries in the clusters array must add up to the total_weight, which defaults to 100. + */ + 'weight': (_google_protobuf_UInt32Value__Output | null); + /** + * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in + * the upstream cluster with metadata matching what is set in this field will be considered for + * load balancing. Note that this will be merged with what's provided in + * :ref:`RouteAction.metadata_match `, with + * values here taking precedence. The filter name should be specified as *envoy.lb*. + */ + 'metadata_match': (_envoy_config_core_v3_Metadata__Output | null); + /** + * Specifies a list of headers to be added to requests when this cluster is selected + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. + * Headers specified at this level are applied before headers from the enclosing + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * Specifies a list of HTTP headers that should be removed from each request when + * this cluster is selected through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. + */ + 'request_headers_to_remove': (string)[]; + /** + * Specifies a list of headers to be added to responses when this cluster is selected + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. + * Headers specified at this level are applied before headers from the enclosing + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * header value syntax, see the documentation on :ref:`custom request headers + * `. + */ + 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; + /** + * Specifies a list of headers to be removed from responses when this cluster is selected + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. + */ + 'response_headers_to_remove': (string)[]; + /** + * The per_filter_config field can be used to provide weighted cluster-specific + * configurations for filters. The key should match the filter name, such as + * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter + * specific; see the :ref:`HTTP filter documentation ` + * for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] + */ + 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); + /** + * Indicates that during forwarding, the host header will be swapped with + * this value. + */ + 'host_rewrite_literal'?: (string); + 'host_rewrite_specifier': "host_rewrite_literal"; +} + +/** + * Compared to the :ref:`cluster ` field that specifies a + * single upstream cluster as the target of a request, the :ref:`weighted_clusters + * ` option allows for specification of + * multiple upstream clusters along with weights that indicate the percentage of + * traffic to be forwarded to each cluster. The router selects an upstream cluster based on the + * weights. + */ +export interface WeightedCluster { + /** + * Specifies one or more upstream clusters associated with the route. + */ + 'clusters'?: (_envoy_config_route_v3_WeightedCluster_ClusterWeight)[]; + /** + * Specifies the runtime key prefix that should be used to construct the + * runtime keys associated with each cluster. When the *runtime_key_prefix* is + * specified, the router will look for weights associated with each upstream + * cluster under the key *runtime_key_prefix* + "." + *cluster[i].name* where + * *cluster[i]* denotes an entry in the clusters array field. If the runtime + * key for the cluster does not exist, the value specified in the + * configuration file will be used as the default weight. See the :ref:`runtime documentation + * ` for how key names map to the underlying implementation. + */ + 'runtime_key_prefix'?: (string); + /** + * Specifies the total weight across all clusters. The sum of all cluster weights must equal this + * value, which must be greater than 0. Defaults to 100. + */ + 'total_weight'?: (_google_protobuf_UInt32Value | null); +} + +/** + * Compared to the :ref:`cluster ` field that specifies a + * single upstream cluster as the target of a request, the :ref:`weighted_clusters + * ` option allows for specification of + * multiple upstream clusters along with weights that indicate the percentage of + * traffic to be forwarded to each cluster. The router selects an upstream cluster based on the + * weights. + */ +export interface WeightedCluster__Output { + /** + * Specifies one or more upstream clusters associated with the route. + */ + 'clusters': (_envoy_config_route_v3_WeightedCluster_ClusterWeight__Output)[]; + /** + * Specifies the runtime key prefix that should be used to construct the + * runtime keys associated with each cluster. When the *runtime_key_prefix* is + * specified, the router will look for weights associated with each upstream + * cluster under the key *runtime_key_prefix* + "." + *cluster[i].name* where + * *cluster[i]* denotes an entry in the clusters array field. If the runtime + * key for the cluster does not exist, the value specified in the + * configuration file will be used as the default weight. See the :ref:`runtime documentation + * ` for how key names map to the underlying implementation. + */ + 'runtime_key_prefix': (string); + /** + * Specifies the total weight across all clusters. The sum of all cluster weights must equal this + * value, which must be greater than 0. Defaults to 100. + */ + 'total_weight': (_google_protobuf_UInt32Value__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts new file mode 100644 index 000000000..9b9859bc5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts @@ -0,0 +1,85 @@ +// Original file: deps/envoy-api/envoy/config/trace/v3/http_tracer.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Configuration for an HTTP tracer provider used by Envoy. + * + * The configuration is defined by the + * :ref:`HttpConnectionManager.Tracing ` + * :ref:`provider ` + * field. + */ +export interface _envoy_config_trace_v3_Tracing_Http { + /** + * The name of the HTTP trace driver to instantiate. The name must match a + * supported HTTP trace driver. + * See the :ref:`extensions listed in typed_config below ` for the default list of the HTTP trace driver. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Trace driver specific configuration which must be set according to the driver being instantiated. + * [#extension-category: envoy.tracers] + */ + 'config_type'?: "typed_config"; +} + +/** + * Configuration for an HTTP tracer provider used by Envoy. + * + * The configuration is defined by the + * :ref:`HttpConnectionManager.Tracing ` + * :ref:`provider ` + * field. + */ +export interface _envoy_config_trace_v3_Tracing_Http__Output { + /** + * The name of the HTTP trace driver to instantiate. The name must match a + * supported HTTP trace driver. + * See the :ref:`extensions listed in typed_config below ` for the default list of the HTTP trace driver. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Trace driver specific configuration which must be set according to the driver being instantiated. + * [#extension-category: envoy.tracers] + */ + 'config_type': "typed_config"; +} + +/** + * The tracing configuration specifies settings for an HTTP tracer provider used by Envoy. + * + * Envoy may support other tracers in the future, but right now the HTTP tracer is the only one + * supported. + * + * .. attention:: + * + * Use of this message type has been deprecated in favor of direct use of + * :ref:`Tracing.Http `. + */ +export interface Tracing { + /** + * Provides configuration for the HTTP tracer. + */ + 'http'?: (_envoy_config_trace_v3_Tracing_Http | null); +} + +/** + * The tracing configuration specifies settings for an HTTP tracer provider used by Envoy. + * + * Envoy may support other tracers in the future, but right now the HTTP tracer is the only one + * supported. + * + * .. attention:: + * + * Use of this message type has been deprecated in favor of direct use of + * :ref:`Tracing.Http `. + */ +export interface Tracing__Output { + /** + * Provides configuration for the HTTP tracer. + */ + 'http': (_envoy_config_trace_v3_Tracing_Http__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts new file mode 100644 index 000000000..bec0403e4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts @@ -0,0 +1,79 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/common/fault/v3/fault.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../../../google/protobuf/Duration'; +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../../../envoy/type/v3/FractionalPercent'; + +// Original file: deps/envoy-api/envoy/extensions/filters/common/fault/v3/fault.proto + +export enum _envoy_extensions_filters_common_fault_v3_FaultDelay_FaultDelayType { + /** + * Unused and deprecated. + */ + FIXED = 0, +} + +/** + * Fault delays are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDelay { +} + +/** + * Fault delays are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDelay__Output { +} + +/** + * Delay specification is used to inject latency into the + * HTTP/Mongo operation. + * [#next-free-field: 6] + */ +export interface FaultDelay { + /** + * Add a fixed delay before forwarding the operation upstream. See + * https://developers.google.com/protocol-buffers/docs/proto3#json for + * the JSON/YAML Duration mapping. For HTTP/Mongo, the specified + * delay will be injected before a new request/operation. + * This is required if type is FIXED. + */ + 'fixed_delay'?: (_google_protobuf_Duration | null); + /** + * The percentage of operations/connections/requests on which the delay will be injected. + */ + 'percentage'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Fault delays are controlled via an HTTP header (if applicable). + */ + 'header_delay'?: (_envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDelay | null); + 'fault_delay_secifier'?: "fixed_delay"|"header_delay"; +} + +/** + * Delay specification is used to inject latency into the + * HTTP/Mongo operation. + * [#next-free-field: 6] + */ +export interface FaultDelay__Output { + /** + * Add a fixed delay before forwarding the operation upstream. See + * https://developers.google.com/protocol-buffers/docs/proto3#json for + * the JSON/YAML Duration mapping. For HTTP/Mongo, the specified + * delay will be injected before a new request/operation. + * This is required if type is FIXED. + */ + 'fixed_delay'?: (_google_protobuf_Duration__Output | null); + /** + * The percentage of operations/connections/requests on which the delay will be injected. + */ + 'percentage': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Fault delays are controlled via an HTTP header (if applicable). + */ + 'header_delay'?: (_envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDelay__Output | null); + 'fault_delay_secifier': "fixed_delay"|"header_delay"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultRateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultRateLimit.ts new file mode 100644 index 000000000..4df7395bb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultRateLimit.ts @@ -0,0 +1,78 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/common/fault/v3/fault.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../../../envoy/type/v3/FractionalPercent'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Describes a fixed/constant rate limit. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultRateLimit_FixedLimit { + /** + * The limit supplied in KiB/s. + */ + 'limit_kbps'?: (number | string | Long); +} + +/** + * Describes a fixed/constant rate limit. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultRateLimit_FixedLimit__Output { + /** + * The limit supplied in KiB/s. + */ + 'limit_kbps': (string); +} + +/** + * Rate limits are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultRateLimit_HeaderLimit { +} + +/** + * Rate limits are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultRateLimit_HeaderLimit__Output { +} + +/** + * Describes a rate limit to be applied. + */ +export interface FaultRateLimit { + /** + * A fixed rate limit. + */ + 'fixed_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit_FixedLimit | null); + /** + * The percentage of operations/connections/requests on which the rate limit will be injected. + */ + 'percentage'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Rate limits are controlled via an HTTP header (if applicable). + */ + 'header_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit_HeaderLimit | null); + 'limit_type'?: "fixed_limit"|"header_limit"; +} + +/** + * Describes a rate limit to be applied. + */ +export interface FaultRateLimit__Output { + /** + * A fixed rate limit. + */ + 'fixed_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit_FixedLimit__Output | null); + /** + * The percentage of operations/connections/requests on which the rate limit will be injected. + */ + 'percentage': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Rate limits are controlled via an HTTP header (if applicable). + */ + 'header_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit_HeaderLimit__Output | null); + 'limit_type': "fixed_limit"|"header_limit"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/FaultAbort.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/FaultAbort.ts new file mode 100644 index 000000000..823706cb7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/FaultAbort.ts @@ -0,0 +1,67 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/http/fault/v3/fault.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../../../envoy/type/v3/FractionalPercent'; + +/** + * Fault aborts are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_http_fault_v3_FaultAbort_HeaderAbort { +} + +/** + * Fault aborts are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_http_fault_v3_FaultAbort_HeaderAbort__Output { +} + +/** + * [#next-free-field: 6] + */ +export interface FaultAbort { + /** + * HTTP status code to use to abort the HTTP request. + */ + 'http_status'?: (number); + /** + * The percentage of requests/operations/connections that will be aborted with the error code + * provided. + */ + 'percentage'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Fault aborts are controlled via an HTTP header (if applicable). + */ + 'header_abort'?: (_envoy_extensions_filters_http_fault_v3_FaultAbort_HeaderAbort | null); + /** + * gRPC status code to use to abort the gRPC request. + */ + 'grpc_status'?: (number); + 'error_type'?: "http_status"|"grpc_status"|"header_abort"; +} + +/** + * [#next-free-field: 6] + */ +export interface FaultAbort__Output { + /** + * HTTP status code to use to abort the HTTP request. + */ + 'http_status'?: (number); + /** + * The percentage of requests/operations/connections that will be aborted with the error code + * provided. + */ + 'percentage': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Fault aborts are controlled via an HTTP header (if applicable). + */ + 'header_abort'?: (_envoy_extensions_filters_http_fault_v3_FaultAbort_HeaderAbort__Output | null); + /** + * gRPC status code to use to abort the gRPC request. + */ + 'grpc_status'?: (number); + 'error_type': "http_status"|"grpc_status"|"header_abort"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts new file mode 100644 index 000000000..d78bd9e4a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts @@ -0,0 +1,227 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/http/fault/v3/fault.proto + +import type { FaultDelay as _envoy_extensions_filters_common_fault_v3_FaultDelay, FaultDelay__Output as _envoy_extensions_filters_common_fault_v3_FaultDelay__Output } from '../../../../../../envoy/extensions/filters/common/fault/v3/FaultDelay'; +import type { FaultAbort as _envoy_extensions_filters_http_fault_v3_FaultAbort, FaultAbort__Output as _envoy_extensions_filters_http_fault_v3_FaultAbort__Output } from '../../../../../../envoy/extensions/filters/http/fault/v3/FaultAbort'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../../../envoy/config/route/v3/HeaderMatcher'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../../google/protobuf/UInt32Value'; +import type { FaultRateLimit as _envoy_extensions_filters_common_fault_v3_FaultRateLimit, FaultRateLimit__Output as _envoy_extensions_filters_common_fault_v3_FaultRateLimit__Output } from '../../../../../../envoy/extensions/filters/common/fault/v3/FaultRateLimit'; + +/** + * [#next-free-field: 16] + */ +export interface HTTPFault { + /** + * If specified, the filter will inject delays based on the values in the + * object. + */ + 'delay'?: (_envoy_extensions_filters_common_fault_v3_FaultDelay | null); + /** + * If specified, the filter will abort requests based on the values in + * the object. At least *abort* or *delay* must be specified. + */ + 'abort'?: (_envoy_extensions_filters_http_fault_v3_FaultAbort | null); + /** + * Specifies the name of the (destination) upstream cluster that the + * filter should match on. Fault injection will be restricted to requests + * bound to the specific upstream cluster. + */ + 'upstream_cluster'?: (string); + /** + * Specifies a set of headers that the filter should match on. The fault + * injection filter can be applied selectively to requests that match a set of + * headers specified in the fault filter config. The chances of actual fault + * injection further depend on the value of the :ref:`percentage + * ` field. + * The filter will check the request's headers against all the specified + * headers in the filter config. A match will happen if all the headers in the + * config are present in the request with the same values (or based on + * presence if the *value* field is not in the config). + */ + 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; + /** + * Faults are injected for the specified list of downstream hosts. If this + * setting is not set, faults are injected for all downstream nodes. + * Downstream node name is taken from :ref:`the HTTP + * x-envoy-downstream-service-node + * ` header and compared + * against downstream_nodes list. + */ + 'downstream_nodes'?: (string)[]; + /** + * The maximum number of faults that can be active at a single time via the configured fault + * filter. Note that because this setting can be overridden at the route level, it's possible + * for the number of active faults to be greater than this value (if injected via a different + * route). If not specified, defaults to unlimited. This setting can be overridden via + * `runtime ` and any faults that are not injected + * due to overflow will be indicated via the `faults_overflow + * ` stat. + * + * .. attention:: + * Like other :ref:`circuit breakers ` in Envoy, this is a fuzzy + * limit. It's possible for the number of active faults to rise slightly above the configured + * amount due to the implementation details. + */ + 'max_active_faults'?: (_google_protobuf_UInt32Value | null); + /** + * The response rate limit to be applied to the response body of the stream. When configured, + * the percentage can be overridden by the :ref:`fault.http.rate_limit.response_percent + * ` runtime key. + * + * .. attention:: + * This is a per-stream limit versus a connection level limit. This means that concurrent streams + * will each get an independent limit. + */ + 'response_rate_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit | null); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.delay.fixed_delay_percent + */ + 'delay_percent_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.abort_percent + */ + 'abort_percent_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.delay.fixed_duration_ms + */ + 'delay_duration_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.http_status + */ + 'abort_http_status_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.max_active_faults + */ + 'max_active_faults_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.rate_limit.response_percent + */ + 'response_rate_limit_percent_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.grpc_status + */ + 'abort_grpc_status_runtime'?: (string); + /** + * To control whether stats storage is allocated dynamically for each downstream server. + * If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + * If set to false, dynamic stats storage will be allocated for the downstream cluster name. + * Default value is false. + */ + 'disable_downstream_cluster_stats'?: (boolean); +} + +/** + * [#next-free-field: 16] + */ +export interface HTTPFault__Output { + /** + * If specified, the filter will inject delays based on the values in the + * object. + */ + 'delay': (_envoy_extensions_filters_common_fault_v3_FaultDelay__Output | null); + /** + * If specified, the filter will abort requests based on the values in + * the object. At least *abort* or *delay* must be specified. + */ + 'abort': (_envoy_extensions_filters_http_fault_v3_FaultAbort__Output | null); + /** + * Specifies the name of the (destination) upstream cluster that the + * filter should match on. Fault injection will be restricted to requests + * bound to the specific upstream cluster. + */ + 'upstream_cluster': (string); + /** + * Specifies a set of headers that the filter should match on. The fault + * injection filter can be applied selectively to requests that match a set of + * headers specified in the fault filter config. The chances of actual fault + * injection further depend on the value of the :ref:`percentage + * ` field. + * The filter will check the request's headers against all the specified + * headers in the filter config. A match will happen if all the headers in the + * config are present in the request with the same values (or based on + * presence if the *value* field is not in the config). + */ + 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; + /** + * Faults are injected for the specified list of downstream hosts. If this + * setting is not set, faults are injected for all downstream nodes. + * Downstream node name is taken from :ref:`the HTTP + * x-envoy-downstream-service-node + * ` header and compared + * against downstream_nodes list. + */ + 'downstream_nodes': (string)[]; + /** + * The maximum number of faults that can be active at a single time via the configured fault + * filter. Note that because this setting can be overridden at the route level, it's possible + * for the number of active faults to be greater than this value (if injected via a different + * route). If not specified, defaults to unlimited. This setting can be overridden via + * `runtime ` and any faults that are not injected + * due to overflow will be indicated via the `faults_overflow + * ` stat. + * + * .. attention:: + * Like other :ref:`circuit breakers ` in Envoy, this is a fuzzy + * limit. It's possible for the number of active faults to rise slightly above the configured + * amount due to the implementation details. + */ + 'max_active_faults': (_google_protobuf_UInt32Value__Output | null); + /** + * The response rate limit to be applied to the response body of the stream. When configured, + * the percentage can be overridden by the :ref:`fault.http.rate_limit.response_percent + * ` runtime key. + * + * .. attention:: + * This is a per-stream limit versus a connection level limit. This means that concurrent streams + * will each get an independent limit. + */ + 'response_rate_limit': (_envoy_extensions_filters_common_fault_v3_FaultRateLimit__Output | null); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.delay.fixed_delay_percent + */ + 'delay_percent_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.abort_percent + */ + 'abort_percent_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.delay.fixed_duration_ms + */ + 'delay_duration_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.http_status + */ + 'abort_http_status_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.max_active_faults + */ + 'max_active_faults_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.rate_limit.response_percent + */ + 'response_rate_limit_percent_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.grpc_status + */ + 'abort_grpc_status_runtime': (string); + /** + * To control whether stats storage is allocated dynamically for each downstream server. + * If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + * If set to false, dynamic stats storage will be allocated for the downstream cluster name. + * Default value is false. + */ + 'disable_downstream_cluster_stats': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/EnvoyMobileHttpConnectionManager.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/EnvoyMobileHttpConnectionManager.ts new file mode 100644 index 000000000..eb73721c3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/EnvoyMobileHttpConnectionManager.ts @@ -0,0 +1,29 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { HttpConnectionManager as _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager, HttpConnectionManager__Output as _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; + +/** + * [#protodoc-title: Envoy Mobile HTTP connection manager] + * HTTP connection manager for use in Envoy mobile. + * [#extension: envoy.filters.network.envoy_mobile_http_connection_manager] + */ +export interface EnvoyMobileHttpConnectionManager { + /** + * The configuration for the underlying HttpConnectionManager which will be + * instantiated for Envoy mobile. + */ + 'config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager | null); +} + +/** + * [#protodoc-title: Envoy Mobile HTTP connection manager] + * HTTP connection manager for use in Envoy mobile. + * [#extension: envoy.filters.network.envoy_mobile_http_connection_manager] + */ +export interface EnvoyMobileHttpConnectionManager__Output { + /** + * The configuration for the underlying HttpConnectionManager which will be + * instantiated for Envoy mobile. + */ + 'config': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts new file mode 100644 index 000000000..fdf7084fe --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts @@ -0,0 +1,1378 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { Rds as _envoy_extensions_filters_network_http_connection_manager_v3_Rds, Rds__Output as _envoy_extensions_filters_network_http_connection_manager_v3_Rds__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/Rds'; +import type { RouteConfiguration as _envoy_config_route_v3_RouteConfiguration, RouteConfiguration__Output as _envoy_config_route_v3_RouteConfiguration__Output } from '../../../../../../envoy/config/route/v3/RouteConfiguration'; +import type { HttpFilter as _envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter, HttpFilter__Output as _envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../../../google/protobuf/BoolValue'; +import type { Http1ProtocolOptions as _envoy_config_core_v3_Http1ProtocolOptions, Http1ProtocolOptions__Output as _envoy_config_core_v3_Http1ProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/Http1ProtocolOptions'; +import type { Http2ProtocolOptions as _envoy_config_core_v3_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_config_core_v3_Http2ProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/Http2ProtocolOptions'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../../../google/protobuf/Duration'; +import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../../../envoy/config/accesslog/v3/AccessLog'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../../google/protobuf/UInt32Value'; +import type { ScopedRoutes as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes, ScopedRoutes__Output as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes'; +import type { HttpProtocolOptions as _envoy_config_core_v3_HttpProtocolOptions, HttpProtocolOptions__Output as _envoy_config_core_v3_HttpProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/HttpProtocolOptions'; +import type { RequestIDExtension as _envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension, RequestIDExtension__Output as _envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension'; +import type { LocalReplyConfig as _envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig, LocalReplyConfig__Output as _envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig'; +import type { Http3ProtocolOptions as _envoy_config_core_v3_Http3ProtocolOptions, Http3ProtocolOptions__Output as _envoy_config_core_v3_Http3ProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/Http3ProtocolOptions'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { SchemeHeaderTransformation as _envoy_config_core_v3_SchemeHeaderTransformation, SchemeHeaderTransformation__Output as _envoy_config_core_v3_SchemeHeaderTransformation__Output } from '../../../../../../envoy/config/core/v3/SchemeHeaderTransformation'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../../../envoy/type/v3/Percent'; +import type { CustomTag as _envoy_type_tracing_v3_CustomTag, CustomTag__Output as _envoy_type_tracing_v3_CustomTag__Output } from '../../../../../../envoy/type/tracing/v3/CustomTag'; +import type { _envoy_config_trace_v3_Tracing_Http, _envoy_config_trace_v3_Tracing_Http__Output } from '../../../../../../envoy/config/trace/v3/Tracing'; +import type { PathTransformation as _envoy_type_http_v3_PathTransformation, PathTransformation__Output as _envoy_type_http_v3_PathTransformation__Output } from '../../../../../../envoy/type/http/v3/PathTransformation'; + +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType { + /** + * For every new connection, the connection manager will determine which + * codec to use. This mode supports both ALPN for TLS listeners as well as + * protocol inference for plaintext listeners. If ALPN data is available, it + * is preferred, otherwise protocol inference is used. In almost all cases, + * this is the right option to choose for this setting. + */ + AUTO = 0, + /** + * The connection manager will assume that the client is speaking HTTP/1.1. + */ + HTTP1 = 1, + /** + * The connection manager will assume that the client is speaking HTTP/2 + * (Envoy does not require HTTP/2 to take place over TLS or to use ALPN. + * Prior knowledge is allowed). + */ + HTTP2 = 2, + /** + * [#not-implemented-hide:] QUIC implementation is not production ready yet. Use this enum with + * caution to prevent accidental execution of QUIC code. I.e. `!= HTTP2` is no longer sufficient + * to distinguish HTTP1 and HTTP2 traffic. + */ + HTTP3 = 3, +} + +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +/** + * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP + * header. + */ +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails { + /** + * Do not send the XFCC header to the next hop. This is the default value. + */ + SANITIZE = 0, + /** + * When the client connection is mTLS (Mutual TLS), forward the XFCC header + * in the request. + */ + FORWARD_ONLY = 1, + /** + * When the client connection is mTLS, append the client certificate + * information to the request’s XFCC header and forward it. + */ + APPEND_FORWARD = 2, + /** + * When the client connection is mTLS, reset the XFCC header with the client + * certificate information and send it to the next hop. + */ + SANITIZE_SET = 3, + /** + * Always forward the XFCC header in the request, regardless of whether the + * client connection is mTLS. + */ + ALWAYS_FORWARD_ONLY = 4, +} + +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig { + /** + * Whether unix socket addresses should be considered internal. + */ + 'unix_sockets'?: (boolean); +} + +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig__Output { + /** + * Whether unix socket addresses should be considered internal. + */ + 'unix_sockets': (boolean); +} + +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing_OperationName { + /** + * The HTTP listener is used for ingress/incoming requests. + */ + INGRESS = 0, + /** + * The HTTP listener is used for egress/outgoing requests. + */ + EGRESS = 1, +} + +/** + * [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied + * before any processing of requests by HTTP filters, routing, and matching. Only the normalized + * path will be visible internally if a transformation is enabled. Any path rewrites that the + * router performs (e.g. :ref:`regex_rewrite + * ` or :ref:`prefix_rewrite + * `) will apply to the *:path* header + * destined for the upstream. + * + * Note: access logging and tracing will show the original *:path* header. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions { + /** + * [#not-implemented-hide:] Normalization applies internally before any processing of requests by + * HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults + * to :ref:`NormalizePathRFC3986 + * `. When not + * specified, this value may be overridden by the runtime variable + * :ref:`http_connection_manager.normalize_path`. + * Envoy will respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 + * normalization due to disallowed characters.) + */ + 'forwarding_transformation'?: (_envoy_type_http_v3_PathTransformation | null); + /** + * [#not-implemented-hide:] Normalization only applies internally before any processing of + * requests by HTTP filters, routing, and matching. These will be applied after full + * transformation is applied. The *:path* header before this transformation will be restored in + * the router filter and sent upstream unless it was mutated by a filter. Defaults to no + * transformations. + * Multiple actions can be applied in the same Transformation, forming a sequential + * pipeline. The transformations will be performed in the order that they appear. Envoy will + * respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 + * normalization due to disallowed characters.) + */ + 'http_filter_transformation'?: (_envoy_type_http_v3_PathTransformation | null); +} + +/** + * [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied + * before any processing of requests by HTTP filters, routing, and matching. Only the normalized + * path will be visible internally if a transformation is enabled. Any path rewrites that the + * router performs (e.g. :ref:`regex_rewrite + * ` or :ref:`prefix_rewrite + * `) will apply to the *:path* header + * destined for the upstream. + * + * Note: access logging and tracing will show the original *:path* header. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions__Output { + /** + * [#not-implemented-hide:] Normalization applies internally before any processing of requests by + * HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults + * to :ref:`NormalizePathRFC3986 + * `. When not + * specified, this value may be overridden by the runtime variable + * :ref:`http_connection_manager.normalize_path`. + * Envoy will respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 + * normalization due to disallowed characters.) + */ + 'forwarding_transformation': (_envoy_type_http_v3_PathTransformation__Output | null); + /** + * [#not-implemented-hide:] Normalization only applies internally before any processing of + * requests by HTTP filters, routing, and matching. These will be applied after full + * transformation is applied. The *:path* header before this transformation will be restored in + * the router filter and sent upstream unless it was mutated by a filter. Defaults to no + * transformations. + * Multiple actions can be applied in the same Transformation, forming a sequential + * pipeline. The transformations will be performed in the order that they appear. Envoy will + * respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 + * normalization due to disallowed characters.) + */ + 'http_filter_transformation': (_envoy_type_http_v3_PathTransformation__Output | null); +} + +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +/** + * Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + * This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + */ +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction { + /** + * Default behavior specific to implementation (i.e. Envoy) of this configuration option. + * Envoy, by default, takes the KEEP_UNCHANGED action. + * NOTE: the implementation may change the default behavior at-will. + */ + IMPLEMENTATION_SPECIFIC_DEFAULT = 0, + /** + * Keep escaped slashes. + */ + KEEP_UNCHANGED = 1, + /** + * Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + * The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + */ + REJECT_REQUEST = 2, + /** + * Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + * Redirect occurs after path normalization and merge slashes transformations if they were configured. + * NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + * This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + * traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + * The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + * redirected request. + */ + UNESCAPE_AND_REDIRECT = 3, + /** + * Unescape %2F and %5C sequences. + * Note: this option should not be enabled if intermediaries perform path based access control as + * it may lead to path confusion vulnerabilities. + */ + UNESCAPE_AND_FORWARD = 4, +} + +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation { + /** + * Overwrite any Server header with the contents of server_name. + */ + OVERWRITE = 0, + /** + * If no Server header is present, append Server server_name + * If a Server header is present, pass it through. + */ + APPEND_IF_ABSENT = 1, + /** + * Pass through the value of the server header, and do not append a header + * if none is present. + */ + PASS_THROUGH = 2, +} + +/** + * [#next-free-field: 7] + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails { + /** + * Whether to forward the subject of the client cert. Defaults to false. + */ + 'subject'?: (_google_protobuf_BoolValue | null); + /** + * Whether to forward the entire client cert in URL encoded PEM format. This will appear in the + * XFCC header comma separated from other values with the value Cert="PEM". + * Defaults to false. + */ + 'cert'?: (boolean); + /** + * Whether to forward the entire client cert chain (including the leaf cert) in URL encoded PEM + * format. This will appear in the XFCC header comma separated from other values with the value + * Chain="PEM". + * Defaults to false. + */ + 'chain'?: (boolean); + /** + * Whether to forward the DNS type Subject Alternative Names of the client cert. + * Defaults to false. + */ + 'dns'?: (boolean); + /** + * Whether to forward the URI type Subject Alternative Name of the client cert. Defaults to + * false. + */ + 'uri'?: (boolean); +} + +/** + * [#next-free-field: 7] + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails__Output { + /** + * Whether to forward the subject of the client cert. Defaults to false. + */ + 'subject': (_google_protobuf_BoolValue__Output | null); + /** + * Whether to forward the entire client cert in URL encoded PEM format. This will appear in the + * XFCC header comma separated from other values with the value Cert="PEM". + * Defaults to false. + */ + 'cert': (boolean); + /** + * Whether to forward the entire client cert chain (including the leaf cert) in URL encoded PEM + * format. This will appear in the XFCC header comma separated from other values with the value + * Chain="PEM". + * Defaults to false. + */ + 'chain': (boolean); + /** + * Whether to forward the DNS type Subject Alternative Names of the client cert. + * Defaults to false. + */ + 'dns': (boolean); + /** + * Whether to forward the URI type Subject Alternative Name of the client cert. Defaults to + * false. + */ + 'uri': (boolean); +} + +/** + * [#next-free-field: 10] + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing { + /** + * Target percentage of requests managed by this HTTP connection manager that will be force + * traced if the :ref:`x-client-trace-id ` + * header is set. This field is a direct analog for the runtime variable + * 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + * `. + * Default: 100% + */ + 'client_sampling'?: (_envoy_type_v3_Percent | null); + /** + * Target percentage of requests managed by this HTTP connection manager that will be randomly + * selected for trace generation, if not requested by the client or not forced. This field is + * a direct analog for the runtime variable 'tracing.random_sampling' in the + * :ref:`HTTP Connection Manager `. + * Default: 100% + */ + 'random_sampling'?: (_envoy_type_v3_Percent | null); + /** + * Target percentage of requests managed by this HTTP connection manager that will be traced + * after all other sampling checks have been applied (client-directed, force tracing, random + * sampling). This field functions as an upper limit on the total configured sampling rate. For + * instance, setting client_sampling to 100% but overall_sampling to 1% will result in only 1% + * of client requests with the appropriate headers to be force traced. This field is a direct + * analog for the runtime variable 'tracing.global_enabled' in the + * :ref:`HTTP Connection Manager `. + * Default: 100% + */ + 'overall_sampling'?: (_envoy_type_v3_Percent | null); + /** + * Whether to annotate spans with additional data. If true, spans will include logs for stream + * events. + */ + 'verbose'?: (boolean); + /** + * Maximum length of the request path to extract and include in the HttpUrl tag. Used to + * truncate lengthy request paths to meet the needs of a tracing backend. + * Default: 256 + */ + 'max_path_tag_length'?: (_google_protobuf_UInt32Value | null); + /** + * A list of custom tags with unique tag name to create tags for the active span. + */ + 'custom_tags'?: (_envoy_type_tracing_v3_CustomTag)[]; + /** + * Configuration for an external tracing provider. + * If not specified, no tracing will be performed. + * + * .. attention:: + * Please be aware that *envoy.tracers.opencensus* provider can only be configured once + * in Envoy lifetime. + * Any attempts to reconfigure it or to use different configurations for different HCM filters + * will be rejected. + * Such a constraint is inherent to OpenCensus itself. It cannot be overcome without changes + * on OpenCensus side. + */ + 'provider'?: (_envoy_config_trace_v3_Tracing_Http | null); +} + +/** + * [#next-free-field: 10] + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing__Output { + /** + * Target percentage of requests managed by this HTTP connection manager that will be force + * traced if the :ref:`x-client-trace-id ` + * header is set. This field is a direct analog for the runtime variable + * 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + * `. + * Default: 100% + */ + 'client_sampling': (_envoy_type_v3_Percent__Output | null); + /** + * Target percentage of requests managed by this HTTP connection manager that will be randomly + * selected for trace generation, if not requested by the client or not forced. This field is + * a direct analog for the runtime variable 'tracing.random_sampling' in the + * :ref:`HTTP Connection Manager `. + * Default: 100% + */ + 'random_sampling': (_envoy_type_v3_Percent__Output | null); + /** + * Target percentage of requests managed by this HTTP connection manager that will be traced + * after all other sampling checks have been applied (client-directed, force tracing, random + * sampling). This field functions as an upper limit on the total configured sampling rate. For + * instance, setting client_sampling to 100% but overall_sampling to 1% will result in only 1% + * of client requests with the appropriate headers to be force traced. This field is a direct + * analog for the runtime variable 'tracing.global_enabled' in the + * :ref:`HTTP Connection Manager `. + * Default: 100% + */ + 'overall_sampling': (_envoy_type_v3_Percent__Output | null); + /** + * Whether to annotate spans with additional data. If true, spans will include logs for stream + * events. + */ + 'verbose': (boolean); + /** + * Maximum length of the request path to extract and include in the HttpUrl tag. Used to + * truncate lengthy request paths to meet the needs of a tracing backend. + * Default: 256 + */ + 'max_path_tag_length': (_google_protobuf_UInt32Value__Output | null); + /** + * A list of custom tags with unique tag name to create tags for the active span. + */ + 'custom_tags': (_envoy_type_tracing_v3_CustomTag__Output)[]; + /** + * Configuration for an external tracing provider. + * If not specified, no tracing will be performed. + * + * .. attention:: + * Please be aware that *envoy.tracers.opencensus* provider can only be configured once + * in Envoy lifetime. + * Any attempts to reconfigure it or to use different configurations for different HCM filters + * will be rejected. + * Such a constraint is inherent to OpenCensus itself. It cannot be overcome without changes + * on OpenCensus side. + */ + 'provider': (_envoy_config_trace_v3_Tracing_Http__Output | null); +} + +/** + * The configuration for HTTP upgrades. + * For each upgrade type desired, an UpgradeConfig must be added. + * + * .. warning:: + * + * The current implementation of upgrade headers does not handle + * multi-valued upgrade headers. Support for multi-valued headers may be + * added in the future if needed. + * + * .. warning:: + * The current implementation of upgrade headers does not work with HTTP/2 + * upstreams. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_UpgradeConfig { + /** + * The case-insensitive name of this upgrade, e.g. "websocket". + * For each upgrade type present in upgrade_configs, requests with + * Upgrade: [upgrade_type] + * will be proxied upstream. + */ + 'upgrade_type'?: (string); + /** + * If present, this represents the filter chain which will be created for + * this type of upgrade. If no filters are present, the filter chain for + * HTTP connections will be used for this upgrade type. + */ + 'filters'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter)[]; + /** + * Determines if upgrades are enabled or disabled by default. Defaults to true. + * This can be overridden on a per-route basis with :ref:`cluster + * ` as documented in the + * :ref:`upgrade documentation `. + */ + 'enabled'?: (_google_protobuf_BoolValue | null); +} + +/** + * The configuration for HTTP upgrades. + * For each upgrade type desired, an UpgradeConfig must be added. + * + * .. warning:: + * + * The current implementation of upgrade headers does not handle + * multi-valued upgrade headers. Support for multi-valued headers may be + * added in the future if needed. + * + * .. warning:: + * The current implementation of upgrade headers does not work with HTTP/2 + * upstreams. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_UpgradeConfig__Output { + /** + * The case-insensitive name of this upgrade, e.g. "websocket". + * For each upgrade type present in upgrade_configs, requests with + * Upgrade: [upgrade_type] + * will be proxied upstream. + */ + 'upgrade_type': (string); + /** + * If present, this represents the filter chain which will be created for + * this type of upgrade. If no filters are present, the filter chain for + * HTTP connections will be used for this upgrade type. + */ + 'filters': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter__Output)[]; + /** + * Determines if upgrades are enabled or disabled by default. Defaults to true. + * This can be overridden on a per-route basis with :ref:`cluster + * ` as documented in the + * :ref:`upgrade documentation `. + */ + 'enabled': (_google_protobuf_BoolValue__Output | null); +} + +/** + * [#next-free-field: 49] + */ +export interface HttpConnectionManager { + /** + * Supplies the type of codec that the connection manager should use. + */ + 'codec_type'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType); + /** + * The human readable prefix to use when emitting statistics for the + * connection manager. See the :ref:`statistics documentation ` for + * more information. + */ + 'stat_prefix'?: (string); + /** + * The connection manager’s route table will be dynamically loaded via the RDS API. + */ + 'rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_Rds | null); + /** + * The route table for the connection manager is static and is specified in this property. + */ + 'route_config'?: (_envoy_config_route_v3_RouteConfiguration | null); + /** + * A list of individual HTTP filters that make up the filter chain for + * requests made to the connection manager. :ref:`Order matters ` + * as the filters are processed sequentially as request events happen. + */ + 'http_filters'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter)[]; + /** + * Whether the connection manager manipulates the :ref:`config_http_conn_man_headers_user-agent` + * and :ref:`config_http_conn_man_headers_downstream-service-cluster` headers. See the linked + * documentation for more information. Defaults to false. + */ + 'add_user_agent'?: (_google_protobuf_BoolValue | null); + /** + * Presence of the object defines whether the connection manager + * emits :ref:`tracing ` data to the :ref:`configured tracing provider + * `. + */ + 'tracing'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing | null); + /** + * Additional HTTP/1 settings that are passed to the HTTP/1 codec. + */ + 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions | null); + /** + * Additional HTTP/2 settings that are passed directly to the HTTP/2 codec. + */ + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); + /** + * An optional override that the connection manager will write to the server + * header in responses. If not set, the default is *envoy*. + */ + 'server_name'?: (string); + /** + * The time that Envoy will wait between sending an HTTP/2 “shutdown + * notification†(GOAWAY frame with max stream ID) and a final GOAWAY frame. + * This is used so that Envoy provides a grace period for new streams that + * race with the final GOAWAY frame. During this grace period, Envoy will + * continue to accept new streams. After the grace period, a final GOAWAY + * frame is sent and Envoy will start refusing new streams. Draining occurs + * both when a connection hits the idle timeout or during general server + * draining. The default grace period is 5000 milliseconds (5 seconds) if this + * option is not specified. + */ + 'drain_timeout'?: (_google_protobuf_Duration | null); + /** + * Configuration for :ref:`HTTP access logs ` + * emitted by the connection manager. + */ + 'access_log'?: (_envoy_config_accesslog_v3_AccessLog)[]; + /** + * If set to true, the connection manager will use the real remote address + * of the client connection when determining internal versus external origin and manipulating + * various headers. If set to false or absent, the connection manager will use the + * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. See the documentation for + * :ref:`config_http_conn_man_headers_x-forwarded-for`, + * :ref:`config_http_conn_man_headers_x-envoy-internal`, and + * :ref:`config_http_conn_man_headers_x-envoy-external-address` for more information. + */ + 'use_remote_address'?: (_google_protobuf_BoolValue | null); + /** + * Whether the connection manager will generate the :ref:`x-request-id + * ` header if it does not exist. This defaults to + * true. Generating a random UUID4 is expensive so in high throughput scenarios where this feature + * is not desired it can be disabled. + */ + 'generate_request_id'?: (_google_protobuf_BoolValue | null); + /** + * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP + * header. + */ + 'forward_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); + /** + * This field is valid only when :ref:`forward_client_cert_details + * ` + * is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in + * the client certificate to be forwarded. Note that in the + * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and + * *By* is always set when the client certificate presents the URI type Subject Alternative Name + * value. + */ + 'set_current_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails | null); + /** + * If proxy_100_continue is true, Envoy will proxy incoming "Expect: + * 100-continue" headers upstream, and forward "100 Continue" responses + * downstream. If this is false or not set, Envoy will instead strip the + * "Expect: 100-continue" header, and send a "100 Continue" response itself. + */ + 'proxy_100_continue'?: (boolean); + /** + * The number of additional ingress proxy hops from the right side of the + * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header to trust when + * determining the origin client's IP address. The default is zero if this option + * is not specified. See the documentation for + * :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. + */ + 'xff_num_trusted_hops'?: (number); + /** + * If + * :ref:`use_remote_address + * ` + * is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is + * an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. + * This is useful for testing compatibility of upstream services that parse the header value. For + * example, 50.0.0.1 is represented as ::FFFF:50.0.0.1. See `IPv4-Mapped IPv6 Addresses + * `_ for details. This will also affect the + * :ref:`config_http_conn_man_headers_x-envoy-external-address` header. See + * :ref:`http_connection_manager.represent_ipv4_remote_address_as_ipv4_mapped_ipv6 + * ` for runtime + * control. + * [#not-implemented-hide:] + */ + 'represent_ipv4_remote_address_as_ipv4_mapped_ipv6'?: (boolean); + /** + * If set, Envoy will not append the remote address to the + * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in + * conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager + * has mutated the request headers. While :ref:`use_remote_address + * ` + * will also suppress XFF addition, it has consequences for logging and other + * Envoy uses of the remote address, so *skip_xff_append* should be used + * when only an elision of XFF addition is intended. + */ + 'skip_xff_append'?: (boolean); + /** + * Via header value to append to request and response headers. If this is + * empty, no via header will be appended. + */ + 'via'?: (string); + 'upgrade_configs'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_UpgradeConfig)[]; + /** + * The stream idle timeout for connections managed by the connection manager. + * If not specified, this defaults to 5 minutes. The default value was selected + * so as not to interfere with any smaller configured timeouts that may have + * existed in configurations prior to the introduction of this feature, while + * introducing robustness to TCP connections that terminate without a FIN. + * + * This idle timeout applies to new streams and is overridable by the + * :ref:`route-level idle_timeout + * `. Even on a stream in + * which the override applies, prior to receipt of the initial request + * headers, the :ref:`stream_idle_timeout + * ` + * applies. Each time an encode/decode event for headers or data is processed + * for the stream, the timer will be reset. If the timeout fires, the stream + * is terminated with a 408 Request Timeout error code if no upstream response + * header has been received, otherwise a stream reset occurs. + * + * This timeout also specifies the amount of time that Envoy will wait for the peer to open enough + * window to write any remaining stream data once the entirety of stream data (local end stream is + * true) has been buffered pending available window. In other words, this timeout defends against + * a peer that does not release enough window to completely write the stream, even though all + * data has been proxied within available flow control windows. If the timeout is hit in this + * case, the :ref:`tx_flush_timeout ` counter will be + * incremented. Note that :ref:`max_stream_duration + * ` does not apply to + * this corner case. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled according to the value for + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + * + * Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due + * to the granularity of events presented to the connection manager. For example, while receiving + * very large request headers, it may be the case that there is traffic regularly arriving on the + * wire while the connection manage is only able to observe the end-of-headers event, hence the + * stream may still idle timeout. + * + * A value of 0 will completely disable the connection manager stream idle + * timeout, although per-route idle timeout overrides will continue to apply. + */ + 'stream_idle_timeout'?: (_google_protobuf_Duration | null); + /** + * Configures what network addresses are considered internal for stats and header sanitation + * purposes. If unspecified, only RFC1918 IP addresses will be considered internal. + * See the documentation for :ref:`config_http_conn_man_headers_x-envoy-internal` for more + * information about internal/external addresses. + */ + 'internal_address_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig | null); + /** + * The delayed close timeout is for downstream connections managed by the HTTP connection manager. + * It is defined as a grace period after connection close processing has been locally initiated + * during which Envoy will wait for the peer to close (i.e., a TCP FIN/RST is received by Envoy + * from the downstream connection) prior to Envoy closing the socket associated with that + * connection. + * NOTE: This timeout is enforced even when the socket associated with the downstream connection + * is pending a flush of the write buffer. However, any progress made writing data to the socket + * will restart the timer associated with this timeout. This means that the total grace period for + * a socket in this state will be + * +. + * + * Delaying Envoy's connection close and giving the peer the opportunity to initiate the close + * sequence mitigates a race condition that exists when downstream clients do not drain/process + * data in a connection's receive buffer after a remote close has been detected via a socket + * write(). This race leads to such clients failing to process the response code sent by Envoy, + * which could result in erroneous downstream processing. + * + * If the timeout triggers, Envoy will close the connection's socket. + * + * The default timeout is 1000 ms if this option is not specified. + * + * .. NOTE:: + * To be useful in avoiding the race condition described above, this timeout must be set + * to *at least* +<100ms to account for + * a reasonable "worst" case processing time for a full iteration of Envoy's event loop>. + * + * .. WARNING:: + * A value of 0 will completely disable delayed close processing. When disabled, the downstream + * connection's socket will be closed immediately after the write flush is completed or will + * never close if the write flush does not complete. + */ + 'delayed_close_timeout'?: (_google_protobuf_Duration | null); + /** + * The amount of time that Envoy will wait for the entire request to be received. + * The timer is activated when the request is initiated, and is disarmed when the last byte of the + * request is sent upstream (i.e. all decoding filters have processed the request), OR when the + * response is initiated. If not specified or set to 0, this timeout is disabled. + */ + 'request_timeout'?: (_google_protobuf_Duration | null); + /** + * The maximum request headers size for incoming connections. + * If unconfigured, the default max request headers allowed is 60 KiB. + * Requests that exceed this limit will receive a 431 response. + */ + 'max_request_headers_kb'?: (_google_protobuf_UInt32Value | null); + /** + * Should paths be normalized according to RFC 3986 before any processing of + * requests by HTTP filters or routing? This affects the upstream *:path* header + * as well. For paths that fail this check, Envoy will respond with 400 to + * paths that are malformed. This defaults to false currently but will default + * true in the future. When not specified, this value may be overridden by the + * runtime variable + * :ref:`http_connection_manager.normalize_path`. + * See `Normalization and Comparison `_ + * for details of normalization. + * Note that Envoy does not perform + * `case normalization `_ + */ + 'normalize_path'?: (_google_protobuf_BoolValue | null); + /** + * A route table will be dynamically assigned to each request based on request attributes + * (e.g., the value of a header). The "routing scopes" (i.e., route tables) and "scope keys" are + * specified in this message. + */ + 'scoped_routes'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes | null); + /** + * Whether the connection manager will keep the :ref:`x-request-id + * ` header if passed for a request that is edge + * (Edge request is the request from external clients to front Envoy) and not reset it, which + * is the current Envoy behaviour. This defaults to false. + */ + 'preserve_external_request_id'?: (boolean); + /** + * Determines if adjacent slashes in the path are merged into one before any processing of + * requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without + * setting this option, incoming requests with path `//dir///file` will not match against route + * with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of + * `HTTP spec `_ and is provided for convenience. + */ + 'merge_slashes'?: (boolean); + /** + * Defines the action to be applied to the Server header on the response path. + * By default, Envoy will overwrite the header with the value specified in + * server_name. + */ + 'server_header_transformation'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation); + /** + * Additional settings for HTTP requests handled by the connection manager. These will be + * applicable to both HTTP1 and HTTP2 requests. + */ + 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions | null); + /** + * The configuration of the request ID extension. This includes operations such as + * generation, validation, and associated tracing operations. If empty, the + * :ref:`UuidRequestIdConfig ` + * default extension is used with default parameters. See the documentation for that extension + * for details on what it does. Customizing the configuration for the default extension can be + * achieved by configuring it explicitly here. For example, to disable trace reason packing, + * the following configuration can be used: + * + * .. validated-code-block:: yaml + * :type-name: envoy.extensions.filters.network.http_connection_manager.v3.RequestIDExtension + * + * typed_config: + * "@type": type.googleapis.com/envoy.extensions.request_id.uuid.v3.UuidRequestIdConfig + * pack_trace_reason: false + * + * [#extension-category: envoy.request_id] + */ + 'request_id_extension'?: (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension | null); + /** + * If set, Envoy will always set :ref:`x-request-id ` header in response. + * If this is false or not set, the request ID is returned in responses only if tracing is forced using + * :ref:`x-envoy-force-trace ` header. + */ + 'always_set_request_id_in_response'?: (boolean); + /** + * The configuration to customize local reply returned by Envoy. It can customize status code, + * body text and response content type. If not specified, status code and text body are hard + * coded in Envoy, the response content type is plain text. + */ + 'local_reply_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig | null); + /** + * Determines if the port part should be removed from host/authority header before any processing + * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + * local port. This affects the upstream host header unless the method is + * CONNECT in which case if no filter adds a port the original port will be restored before headers are + * sent upstream. + * Without setting this option, incoming requests with host `example:443` will not match against + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * of `HTTP spec `_ and is provided for convenience. + * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + */ + 'strip_matching_host_port'?: (boolean); + /** + * Governs Envoy's behavior when receiving invalid HTTP from downstream. + * If this option is false (default), Envoy will err on the conservative side handling HTTP + * errors, terminating both HTTP/1.1 and HTTP/2 connections when receiving an invalid request. + * If this option is set to true, Envoy will be more permissive, only resetting the invalid + * stream in the case of HTTP/2 and leaving the connection open where possible (if the entire + * request is read for HTTP/1.1) + * In general this should be true for deployments receiving trusted traffic (L2 Envoys, + * company-internal mesh) and false when receiving untrusted traffic (edge deployments). + * + * If different behaviors for invalid_http_message for HTTP/1 and HTTP/2 are + * desired, one should use the new HTTP/1 option :ref:`override_stream_error_on_invalid_http_message + * ` or the new HTTP/2 option + * :ref:`override_stream_error_on_invalid_http_message + * ` + * *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging + * ` + */ + 'stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); + /** + * The amount of time that Envoy will wait for the request headers to be received. The timer is + * activated when the first byte of the headers is received, and is disarmed when the last byte of + * the headers has been received. If not specified or set to 0, this timeout is disabled. + */ + 'request_headers_timeout'?: (_google_protobuf_Duration | null); + /** + * Determines if the port part should be removed from host/authority header before any processing + * of request by HTTP filters or routing. + * This affects the upstream host header unless the method is CONNECT in + * which case if no filter adds a port the original port will be restored before headers are sent upstream. + * Without setting this option, incoming requests with host `example:443` will not match against + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * of `HTTP spec `_ and is provided for convenience. + * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + */ + 'strip_any_host_port'?: (boolean); + /** + * [#not-implemented-hide:] Path normalization configuration. This includes + * configurations for transformations (e.g. RFC 3986 normalization or merge + * adjacent slashes) and the policy to apply them. The policy determines + * whether transformations affect the forwarded *:path* header. RFC 3986 path + * normalization is enabled by default and the default policy is that the + * normalized header will be forwarded. See :ref:`PathNormalizationOptions + * ` + * for details. + */ + 'path_normalization_options'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions | null); + /** + * Additional HTTP/3 settings that are passed directly to the HTTP/3 codec. + * [#not-implemented-hide:] + */ + 'http3_protocol_options'?: (_envoy_config_core_v3_Http3ProtocolOptions | null); + /** + * Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + * The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + * runtime variable. + * The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + * variable can be used to apply the action to a portion of all requests. + */ + 'path_with_escaped_slashes_action'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); + /** + * The configuration for the original IP detection extensions. + * + * When configured the extensions will be called along with the request headers + * and information about the downstream connection, such as the directly connected address. + * Each extension will then use these parameters to decide the request's effective remote address. + * If an extension fails to detect the original IP address and isn't configured to reject + * the request, the HCM will try the remaining extensions until one succeeds or rejects + * the request. If the request isn't rejected nor any extension succeeds, the HCM will + * fallback to using the remote address. + * + * .. WARNING:: + * Extensions cannot be used in conjunction with :ref:`use_remote_address + * ` + * nor :ref:`xff_num_trusted_hops + * `. + * + * [#extension-category: envoy.http.original_ip_detection] + */ + 'original_ip_detection_extensions'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Determines if trailing dot of the host should be removed from host/authority header before any + * processing of request by HTTP filters or routing. + * This affects the upstream host header. + * Without setting this option, incoming requests with host `example.com.` will not match against + * route with :ref:`domains` match set to `example.com`. Defaults to `false`. + * When the incoming request contains a host/authority header that includes a port number, + * setting this option will strip a trailing dot, if present, from the host section, + * leaving the port as is (e.g. host value `example.com.:443` will be updated to `example.com:443`). + */ + 'strip_trailing_host_dot'?: (boolean); + /** + * Allows for explicit transformation of the :scheme header on the request path. + * If not set, Envoy's default :ref:`scheme ` + * handling applies. + */ + 'scheme_header_transformation'?: (_envoy_config_core_v3_SchemeHeaderTransformation | null); + 'route_specifier'?: "rds"|"route_config"|"scoped_routes"; + 'strip_port_mode'?: "strip_any_host_port"; +} + +/** + * [#next-free-field: 49] + */ +export interface HttpConnectionManager__Output { + /** + * Supplies the type of codec that the connection manager should use. + */ + 'codec_type': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType); + /** + * The human readable prefix to use when emitting statistics for the + * connection manager. See the :ref:`statistics documentation ` for + * more information. + */ + 'stat_prefix': (string); + /** + * The connection manager’s route table will be dynamically loaded via the RDS API. + */ + 'rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_Rds__Output | null); + /** + * The route table for the connection manager is static and is specified in this property. + */ + 'route_config'?: (_envoy_config_route_v3_RouteConfiguration__Output | null); + /** + * A list of individual HTTP filters that make up the filter chain for + * requests made to the connection manager. :ref:`Order matters ` + * as the filters are processed sequentially as request events happen. + */ + 'http_filters': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter__Output)[]; + /** + * Whether the connection manager manipulates the :ref:`config_http_conn_man_headers_user-agent` + * and :ref:`config_http_conn_man_headers_downstream-service-cluster` headers. See the linked + * documentation for more information. Defaults to false. + */ + 'add_user_agent': (_google_protobuf_BoolValue__Output | null); + /** + * Presence of the object defines whether the connection manager + * emits :ref:`tracing ` data to the :ref:`configured tracing provider + * `. + */ + 'tracing': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing__Output | null); + /** + * Additional HTTP/1 settings that are passed to the HTTP/1 codec. + */ + 'http_protocol_options': (_envoy_config_core_v3_Http1ProtocolOptions__Output | null); + /** + * Additional HTTP/2 settings that are passed directly to the HTTP/2 codec. + */ + 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); + /** + * An optional override that the connection manager will write to the server + * header in responses. If not set, the default is *envoy*. + */ + 'server_name': (string); + /** + * The time that Envoy will wait between sending an HTTP/2 “shutdown + * notification†(GOAWAY frame with max stream ID) and a final GOAWAY frame. + * This is used so that Envoy provides a grace period for new streams that + * race with the final GOAWAY frame. During this grace period, Envoy will + * continue to accept new streams. After the grace period, a final GOAWAY + * frame is sent and Envoy will start refusing new streams. Draining occurs + * both when a connection hits the idle timeout or during general server + * draining. The default grace period is 5000 milliseconds (5 seconds) if this + * option is not specified. + */ + 'drain_timeout': (_google_protobuf_Duration__Output | null); + /** + * Configuration for :ref:`HTTP access logs ` + * emitted by the connection manager. + */ + 'access_log': (_envoy_config_accesslog_v3_AccessLog__Output)[]; + /** + * If set to true, the connection manager will use the real remote address + * of the client connection when determining internal versus external origin and manipulating + * various headers. If set to false or absent, the connection manager will use the + * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. See the documentation for + * :ref:`config_http_conn_man_headers_x-forwarded-for`, + * :ref:`config_http_conn_man_headers_x-envoy-internal`, and + * :ref:`config_http_conn_man_headers_x-envoy-external-address` for more information. + */ + 'use_remote_address': (_google_protobuf_BoolValue__Output | null); + /** + * Whether the connection manager will generate the :ref:`x-request-id + * ` header if it does not exist. This defaults to + * true. Generating a random UUID4 is expensive so in high throughput scenarios where this feature + * is not desired it can be disabled. + */ + 'generate_request_id': (_google_protobuf_BoolValue__Output | null); + /** + * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP + * header. + */ + 'forward_client_cert_details': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); + /** + * This field is valid only when :ref:`forward_client_cert_details + * ` + * is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in + * the client certificate to be forwarded. Note that in the + * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and + * *By* is always set when the client certificate presents the URI type Subject Alternative Name + * value. + */ + 'set_current_client_cert_details': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails__Output | null); + /** + * If proxy_100_continue is true, Envoy will proxy incoming "Expect: + * 100-continue" headers upstream, and forward "100 Continue" responses + * downstream. If this is false or not set, Envoy will instead strip the + * "Expect: 100-continue" header, and send a "100 Continue" response itself. + */ + 'proxy_100_continue': (boolean); + /** + * The number of additional ingress proxy hops from the right side of the + * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header to trust when + * determining the origin client's IP address. The default is zero if this option + * is not specified. See the documentation for + * :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. + */ + 'xff_num_trusted_hops': (number); + /** + * If + * :ref:`use_remote_address + * ` + * is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is + * an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. + * This is useful for testing compatibility of upstream services that parse the header value. For + * example, 50.0.0.1 is represented as ::FFFF:50.0.0.1. See `IPv4-Mapped IPv6 Addresses + * `_ for details. This will also affect the + * :ref:`config_http_conn_man_headers_x-envoy-external-address` header. See + * :ref:`http_connection_manager.represent_ipv4_remote_address_as_ipv4_mapped_ipv6 + * ` for runtime + * control. + * [#not-implemented-hide:] + */ + 'represent_ipv4_remote_address_as_ipv4_mapped_ipv6': (boolean); + /** + * If set, Envoy will not append the remote address to the + * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in + * conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager + * has mutated the request headers. While :ref:`use_remote_address + * ` + * will also suppress XFF addition, it has consequences for logging and other + * Envoy uses of the remote address, so *skip_xff_append* should be used + * when only an elision of XFF addition is intended. + */ + 'skip_xff_append': (boolean); + /** + * Via header value to append to request and response headers. If this is + * empty, no via header will be appended. + */ + 'via': (string); + 'upgrade_configs': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_UpgradeConfig__Output)[]; + /** + * The stream idle timeout for connections managed by the connection manager. + * If not specified, this defaults to 5 minutes. The default value was selected + * so as not to interfere with any smaller configured timeouts that may have + * existed in configurations prior to the introduction of this feature, while + * introducing robustness to TCP connections that terminate without a FIN. + * + * This idle timeout applies to new streams and is overridable by the + * :ref:`route-level idle_timeout + * `. Even on a stream in + * which the override applies, prior to receipt of the initial request + * headers, the :ref:`stream_idle_timeout + * ` + * applies. Each time an encode/decode event for headers or data is processed + * for the stream, the timer will be reset. If the timeout fires, the stream + * is terminated with a 408 Request Timeout error code if no upstream response + * header has been received, otherwise a stream reset occurs. + * + * This timeout also specifies the amount of time that Envoy will wait for the peer to open enough + * window to write any remaining stream data once the entirety of stream data (local end stream is + * true) has been buffered pending available window. In other words, this timeout defends against + * a peer that does not release enough window to completely write the stream, even though all + * data has been proxied within available flow control windows. If the timeout is hit in this + * case, the :ref:`tx_flush_timeout ` counter will be + * incremented. Note that :ref:`max_stream_duration + * ` does not apply to + * this corner case. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled according to the value for + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + * + * Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due + * to the granularity of events presented to the connection manager. For example, while receiving + * very large request headers, it may be the case that there is traffic regularly arriving on the + * wire while the connection manage is only able to observe the end-of-headers event, hence the + * stream may still idle timeout. + * + * A value of 0 will completely disable the connection manager stream idle + * timeout, although per-route idle timeout overrides will continue to apply. + */ + 'stream_idle_timeout': (_google_protobuf_Duration__Output | null); + /** + * Configures what network addresses are considered internal for stats and header sanitation + * purposes. If unspecified, only RFC1918 IP addresses will be considered internal. + * See the documentation for :ref:`config_http_conn_man_headers_x-envoy-internal` for more + * information about internal/external addresses. + */ + 'internal_address_config': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig__Output | null); + /** + * The delayed close timeout is for downstream connections managed by the HTTP connection manager. + * It is defined as a grace period after connection close processing has been locally initiated + * during which Envoy will wait for the peer to close (i.e., a TCP FIN/RST is received by Envoy + * from the downstream connection) prior to Envoy closing the socket associated with that + * connection. + * NOTE: This timeout is enforced even when the socket associated with the downstream connection + * is pending a flush of the write buffer. However, any progress made writing data to the socket + * will restart the timer associated with this timeout. This means that the total grace period for + * a socket in this state will be + * +. + * + * Delaying Envoy's connection close and giving the peer the opportunity to initiate the close + * sequence mitigates a race condition that exists when downstream clients do not drain/process + * data in a connection's receive buffer after a remote close has been detected via a socket + * write(). This race leads to such clients failing to process the response code sent by Envoy, + * which could result in erroneous downstream processing. + * + * If the timeout triggers, Envoy will close the connection's socket. + * + * The default timeout is 1000 ms if this option is not specified. + * + * .. NOTE:: + * To be useful in avoiding the race condition described above, this timeout must be set + * to *at least* +<100ms to account for + * a reasonable "worst" case processing time for a full iteration of Envoy's event loop>. + * + * .. WARNING:: + * A value of 0 will completely disable delayed close processing. When disabled, the downstream + * connection's socket will be closed immediately after the write flush is completed or will + * never close if the write flush does not complete. + */ + 'delayed_close_timeout': (_google_protobuf_Duration__Output | null); + /** + * The amount of time that Envoy will wait for the entire request to be received. + * The timer is activated when the request is initiated, and is disarmed when the last byte of the + * request is sent upstream (i.e. all decoding filters have processed the request), OR when the + * response is initiated. If not specified or set to 0, this timeout is disabled. + */ + 'request_timeout': (_google_protobuf_Duration__Output | null); + /** + * The maximum request headers size for incoming connections. + * If unconfigured, the default max request headers allowed is 60 KiB. + * Requests that exceed this limit will receive a 431 response. + */ + 'max_request_headers_kb': (_google_protobuf_UInt32Value__Output | null); + /** + * Should paths be normalized according to RFC 3986 before any processing of + * requests by HTTP filters or routing? This affects the upstream *:path* header + * as well. For paths that fail this check, Envoy will respond with 400 to + * paths that are malformed. This defaults to false currently but will default + * true in the future. When not specified, this value may be overridden by the + * runtime variable + * :ref:`http_connection_manager.normalize_path`. + * See `Normalization and Comparison `_ + * for details of normalization. + * Note that Envoy does not perform + * `case normalization `_ + */ + 'normalize_path': (_google_protobuf_BoolValue__Output | null); + /** + * A route table will be dynamically assigned to each request based on request attributes + * (e.g., the value of a header). The "routing scopes" (i.e., route tables) and "scope keys" are + * specified in this message. + */ + 'scoped_routes'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes__Output | null); + /** + * Whether the connection manager will keep the :ref:`x-request-id + * ` header if passed for a request that is edge + * (Edge request is the request from external clients to front Envoy) and not reset it, which + * is the current Envoy behaviour. This defaults to false. + */ + 'preserve_external_request_id': (boolean); + /** + * Determines if adjacent slashes in the path are merged into one before any processing of + * requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without + * setting this option, incoming requests with path `//dir///file` will not match against route + * with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of + * `HTTP spec `_ and is provided for convenience. + */ + 'merge_slashes': (boolean); + /** + * Defines the action to be applied to the Server header on the response path. + * By default, Envoy will overwrite the header with the value specified in + * server_name. + */ + 'server_header_transformation': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation); + /** + * Additional settings for HTTP requests handled by the connection manager. These will be + * applicable to both HTTP1 and HTTP2 requests. + */ + 'common_http_protocol_options': (_envoy_config_core_v3_HttpProtocolOptions__Output | null); + /** + * The configuration of the request ID extension. This includes operations such as + * generation, validation, and associated tracing operations. If empty, the + * :ref:`UuidRequestIdConfig ` + * default extension is used with default parameters. See the documentation for that extension + * for details on what it does. Customizing the configuration for the default extension can be + * achieved by configuring it explicitly here. For example, to disable trace reason packing, + * the following configuration can be used: + * + * .. validated-code-block:: yaml + * :type-name: envoy.extensions.filters.network.http_connection_manager.v3.RequestIDExtension + * + * typed_config: + * "@type": type.googleapis.com/envoy.extensions.request_id.uuid.v3.UuidRequestIdConfig + * pack_trace_reason: false + * + * [#extension-category: envoy.request_id] + */ + 'request_id_extension': (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension__Output | null); + /** + * If set, Envoy will always set :ref:`x-request-id ` header in response. + * If this is false or not set, the request ID is returned in responses only if tracing is forced using + * :ref:`x-envoy-force-trace ` header. + */ + 'always_set_request_id_in_response': (boolean); + /** + * The configuration to customize local reply returned by Envoy. It can customize status code, + * body text and response content type. If not specified, status code and text body are hard + * coded in Envoy, the response content type is plain text. + */ + 'local_reply_config': (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig__Output | null); + /** + * Determines if the port part should be removed from host/authority header before any processing + * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + * local port. This affects the upstream host header unless the method is + * CONNECT in which case if no filter adds a port the original port will be restored before headers are + * sent upstream. + * Without setting this option, incoming requests with host `example:443` will not match against + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * of `HTTP spec `_ and is provided for convenience. + * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + */ + 'strip_matching_host_port': (boolean); + /** + * Governs Envoy's behavior when receiving invalid HTTP from downstream. + * If this option is false (default), Envoy will err on the conservative side handling HTTP + * errors, terminating both HTTP/1.1 and HTTP/2 connections when receiving an invalid request. + * If this option is set to true, Envoy will be more permissive, only resetting the invalid + * stream in the case of HTTP/2 and leaving the connection open where possible (if the entire + * request is read for HTTP/1.1) + * In general this should be true for deployments receiving trusted traffic (L2 Envoys, + * company-internal mesh) and false when receiving untrusted traffic (edge deployments). + * + * If different behaviors for invalid_http_message for HTTP/1 and HTTP/2 are + * desired, one should use the new HTTP/1 option :ref:`override_stream_error_on_invalid_http_message + * ` or the new HTTP/2 option + * :ref:`override_stream_error_on_invalid_http_message + * ` + * *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging + * ` + */ + 'stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); + /** + * The amount of time that Envoy will wait for the request headers to be received. The timer is + * activated when the first byte of the headers is received, and is disarmed when the last byte of + * the headers has been received. If not specified or set to 0, this timeout is disabled. + */ + 'request_headers_timeout': (_google_protobuf_Duration__Output | null); + /** + * Determines if the port part should be removed from host/authority header before any processing + * of request by HTTP filters or routing. + * This affects the upstream host header unless the method is CONNECT in + * which case if no filter adds a port the original port will be restored before headers are sent upstream. + * Without setting this option, incoming requests with host `example:443` will not match against + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * of `HTTP spec `_ and is provided for convenience. + * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + */ + 'strip_any_host_port'?: (boolean); + /** + * [#not-implemented-hide:] Path normalization configuration. This includes + * configurations for transformations (e.g. RFC 3986 normalization or merge + * adjacent slashes) and the policy to apply them. The policy determines + * whether transformations affect the forwarded *:path* header. RFC 3986 path + * normalization is enabled by default and the default policy is that the + * normalized header will be forwarded. See :ref:`PathNormalizationOptions + * ` + * for details. + */ + 'path_normalization_options': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions__Output | null); + /** + * Additional HTTP/3 settings that are passed directly to the HTTP/3 codec. + * [#not-implemented-hide:] + */ + 'http3_protocol_options': (_envoy_config_core_v3_Http3ProtocolOptions__Output | null); + /** + * Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + * The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + * runtime variable. + * The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + * variable can be used to apply the action to a portion of all requests. + */ + 'path_with_escaped_slashes_action': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); + /** + * The configuration for the original IP detection extensions. + * + * When configured the extensions will be called along with the request headers + * and information about the downstream connection, such as the directly connected address. + * Each extension will then use these parameters to decide the request's effective remote address. + * If an extension fails to detect the original IP address and isn't configured to reject + * the request, the HCM will try the remaining extensions until one succeeds or rejects + * the request. If the request isn't rejected nor any extension succeeds, the HCM will + * fallback to using the remote address. + * + * .. WARNING:: + * Extensions cannot be used in conjunction with :ref:`use_remote_address + * ` + * nor :ref:`xff_num_trusted_hops + * `. + * + * [#extension-category: envoy.http.original_ip_detection] + */ + 'original_ip_detection_extensions': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Determines if trailing dot of the host should be removed from host/authority header before any + * processing of request by HTTP filters or routing. + * This affects the upstream host header. + * Without setting this option, incoming requests with host `example.com.` will not match against + * route with :ref:`domains` match set to `example.com`. Defaults to `false`. + * When the incoming request contains a host/authority header that includes a port number, + * setting this option will strip a trailing dot, if present, from the host section, + * leaving the port as is (e.g. host value `example.com.:443` will be updated to `example.com:443`). + */ + 'strip_trailing_host_dot': (boolean); + /** + * Allows for explicit transformation of the :scheme header on the request path. + * If not set, Envoy's default :ref:`scheme ` + * handling applies. + */ + 'scheme_header_transformation': (_envoy_config_core_v3_SchemeHeaderTransformation__Output | null); + 'route_specifier': "rds"|"route_config"|"scoped_routes"; + 'strip_port_mode': "strip_any_host_port"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts new file mode 100644 index 000000000..d1c1cbc06 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts @@ -0,0 +1,86 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../../google/protobuf/Any'; +import type { ExtensionConfigSource as _envoy_config_core_v3_ExtensionConfigSource, ExtensionConfigSource__Output as _envoy_config_core_v3_ExtensionConfigSource__Output } from '../../../../../../envoy/config/core/v3/ExtensionConfigSource'; + +/** + * [#next-free-field: 7] + */ +export interface HttpFilter { + /** + * The name of the filter configuration. The name is used as a fallback to + * select an extension if the type of the configuration proto is not + * sufficient. It also serves as a resource name in ExtensionConfigDS. + */ + 'name'?: (string); + /** + * Filter specific configuration which depends on the filter being instantiated. See the supported + * filters for further documentation. + * + * To support configuring a :ref:`match tree `, use an + * :ref:`ExtensionWithMatcher ` + * with the desired HTTP filter. + * [#extension-category: envoy.filters.http] + */ + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Configuration source specifier for an extension configuration discovery service. + * In case of a failure and without the default configuration, the HTTP listener responds with code 500. + * Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). + * + * To support configuring a :ref:`match tree `, use an + * :ref:`ExtensionWithMatcher ` + * with the desired HTTP filter. This works for both the default filter configuration as well + * as for filters provided via the API. + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource | null); + /** + * If true, clients that do not support this filter may ignore the + * filter but otherwise accept the config. + * Otherwise, clients that do not support this filter must reject the config. + * This is also same with typed per filter config. + */ + 'is_optional'?: (boolean); + 'config_type'?: "typed_config"|"config_discovery"; +} + +/** + * [#next-free-field: 7] + */ +export interface HttpFilter__Output { + /** + * The name of the filter configuration. The name is used as a fallback to + * select an extension if the type of the configuration proto is not + * sufficient. It also serves as a resource name in ExtensionConfigDS. + */ + 'name': (string); + /** + * Filter specific configuration which depends on the filter being instantiated. See the supported + * filters for further documentation. + * + * To support configuring a :ref:`match tree `, use an + * :ref:`ExtensionWithMatcher ` + * with the desired HTTP filter. + * [#extension-category: envoy.filters.http] + */ + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Configuration source specifier for an extension configuration discovery service. + * In case of a failure and without the default configuration, the HTTP listener responds with code 500. + * Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). + * + * To support configuring a :ref:`match tree `, use an + * :ref:`ExtensionWithMatcher ` + * with the desired HTTP filter. This works for both the default filter configuration as well + * as for filters provided via the API. + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output | null); + /** + * If true, clients that do not support this filter may ignore the + * filter but otherwise accept the config. + * Otherwise, clients that do not support this filter must reject the config. + * This is also same with typed per filter config. + */ + 'is_optional': (boolean); + 'config_type': "typed_config"|"config_discovery"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts new file mode 100644 index 000000000..04de11fcb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts @@ -0,0 +1,106 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { ResponseMapper as _envoy_extensions_filters_network_http_connection_manager_v3_ResponseMapper, ResponseMapper__Output as _envoy_extensions_filters_network_http_connection_manager_v3_ResponseMapper__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper'; +import type { SubstitutionFormatString as _envoy_config_core_v3_SubstitutionFormatString, SubstitutionFormatString__Output as _envoy_config_core_v3_SubstitutionFormatString__Output } from '../../../../../../envoy/config/core/v3/SubstitutionFormatString'; + +/** + * The configuration to customize local reply returned by Envoy. + */ +export interface LocalReplyConfig { + /** + * Configuration of list of mappers which allows to filter and change local response. + * The mappers will be checked by the specified order until one is matched. + */ + 'mappers'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ResponseMapper)[]; + /** + * The configuration to form response body from the :ref:`command operators ` + * and to specify response content type as one of: plain/text or application/json. + * + * Example one: "plain/text" ``body_format``. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * The following response body in "plain/text" format will be generated for a request with + * local reply body of "upstream connection error", response_code=503 and path=/foo. + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + * + * Example two: "application/json" ``body_format``. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * json_format: + * status: "%RESPONSE_CODE%" + * message: "%LOCAL_REPLY_BODY%" + * path: "%REQ(:path)%" + * + * The following response body in "application/json" format would be generated for a request with + * local reply body of "upstream connection error", response_code=503 and path=/foo. + * + * .. code-block:: json + * + * { + * "status": 503, + * "message": "upstream connection error", + * "path": "/foo" + * } + */ + 'body_format'?: (_envoy_config_core_v3_SubstitutionFormatString | null); +} + +/** + * The configuration to customize local reply returned by Envoy. + */ +export interface LocalReplyConfig__Output { + /** + * Configuration of list of mappers which allows to filter and change local response. + * The mappers will be checked by the specified order until one is matched. + */ + 'mappers': (_envoy_extensions_filters_network_http_connection_manager_v3_ResponseMapper__Output)[]; + /** + * The configuration to form response body from the :ref:`command operators ` + * and to specify response content type as one of: plain/text or application/json. + * + * Example one: "plain/text" ``body_format``. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * The following response body in "plain/text" format will be generated for a request with + * local reply body of "upstream connection error", response_code=503 and path=/foo. + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + * + * Example two: "application/json" ``body_format``. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * json_format: + * status: "%RESPONSE_CODE%" + * message: "%LOCAL_REPLY_BODY%" + * path: "%REQ(:path)%" + * + * The following response body in "application/json" format would be generated for a request with + * local reply body of "upstream connection error", response_code=503 and path=/foo. + * + * .. code-block:: json + * + * { + * "status": 503, + * "message": "upstream connection error", + * "path": "/foo" + * } + */ + 'body_format': (_envoy_config_core_v3_SubstitutionFormatString__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts new file mode 100644 index 000000000..b99e2f1bd --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts @@ -0,0 +1,31 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../../envoy/config/core/v3/ConfigSource'; + +export interface Rds { + /** + * Configuration source specifier for RDS. + */ + 'config_source'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * The name of the route configuration. This name will be passed to the RDS + * API. This allows an Envoy configuration with multiple HTTP listeners (and + * associated HTTP connection manager filters) to use different route + * configurations. + */ + 'route_config_name'?: (string); +} + +export interface Rds__Output { + /** + * Configuration source specifier for RDS. + */ + 'config_source': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * The name of the route configuration. This name will be passed to the RDS + * API. This allows an Envoy configuration with multiple HTTP listeners (and + * associated HTTP connection manager filters) to use different route + * configurations. + */ + 'route_config_name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts new file mode 100644 index 000000000..ba1789a80 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../../google/protobuf/Any'; + +export interface RequestIDExtension { + /** + * Request ID extension specific configuration. + */ + 'typed_config'?: (_google_protobuf_Any | null); +} + +export interface RequestIDExtension__Output { + /** + * Request ID extension specific configuration. + */ + 'typed_config': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts new file mode 100644 index 000000000..26d14a07d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts @@ -0,0 +1,67 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { AccessLogFilter as _envoy_config_accesslog_v3_AccessLogFilter, AccessLogFilter__Output as _envoy_config_accesslog_v3_AccessLogFilter__Output } from '../../../../../../envoy/config/accesslog/v3/AccessLogFilter'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../../google/protobuf/UInt32Value'; +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../../envoy/config/core/v3/DataSource'; +import type { SubstitutionFormatString as _envoy_config_core_v3_SubstitutionFormatString, SubstitutionFormatString__Output as _envoy_config_core_v3_SubstitutionFormatString__Output } from '../../../../../../envoy/config/core/v3/SubstitutionFormatString'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../../../envoy/config/core/v3/HeaderValueOption'; + +/** + * The configuration to filter and change local response. + * [#next-free-field: 6] + */ +export interface ResponseMapper { + /** + * Filter to determine if this mapper should apply. + */ + 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter | null); + /** + * The new response status code if specified. + */ + 'status_code'?: (_google_protobuf_UInt32Value | null); + /** + * The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` + * command operator in the `body_format`. + */ + 'body'?: (_envoy_config_core_v3_DataSource | null); + /** + * A per mapper `body_format` to override the :ref:`body_format `. + * It will be used when this mapper is matched. + */ + 'body_format_override'?: (_envoy_config_core_v3_SubstitutionFormatString | null); + /** + * HTTP headers to add to a local reply. This allows the response mapper to append, to add + * or to override headers of any local reply before it is sent to a downstream client. + */ + 'headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; +} + +/** + * The configuration to filter and change local response. + * [#next-free-field: 6] + */ +export interface ResponseMapper__Output { + /** + * Filter to determine if this mapper should apply. + */ + 'filter': (_envoy_config_accesslog_v3_AccessLogFilter__Output | null); + /** + * The new response status code if specified. + */ + 'status_code': (_google_protobuf_UInt32Value__Output | null); + /** + * The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` + * command operator in the `body_format`. + */ + 'body': (_envoy_config_core_v3_DataSource__Output | null); + /** + * A per mapper `body_format` to override the :ref:`body_format `. + * It will be used when this mapper is matched. + */ + 'body_format_override': (_envoy_config_core_v3_SubstitutionFormatString__Output | null); + /** + * HTTP headers to add to a local reply. This allows the response mapper to append, to add + * or to override headers of any local reply before it is sent to a downstream client. + */ + 'headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts new file mode 100644 index 000000000..5f7d2b6fe --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts @@ -0,0 +1,27 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../../envoy/config/core/v3/ConfigSource'; + +export interface ScopedRds { + /** + * Configuration source specifier for scoped RDS. + */ + 'scoped_rds_config_source'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * xdstp:// resource locator for scoped RDS collection. + * [#not-implemented-hide:] + */ + 'srds_resources_locator'?: (string); +} + +export interface ScopedRds__Output { + /** + * Configuration source specifier for scoped RDS. + */ + 'scoped_rds_config_source': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * xdstp:// resource locator for scoped RDS collection. + * [#not-implemented-hide:] + */ + 'srds_resources_locator': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList.ts new file mode 100644 index 000000000..e7f05e340 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { ScopedRouteConfiguration as _envoy_config_route_v3_ScopedRouteConfiguration, ScopedRouteConfiguration__Output as _envoy_config_route_v3_ScopedRouteConfiguration__Output } from '../../../../../../envoy/config/route/v3/ScopedRouteConfiguration'; + +/** + * This message is used to work around the limitations with 'oneof' and repeated fields. + */ +export interface ScopedRouteConfigurationsList { + 'scoped_route_configurations'?: (_envoy_config_route_v3_ScopedRouteConfiguration)[]; +} + +/** + * This message is used to work around the limitations with 'oneof' and repeated fields. + */ +export interface ScopedRouteConfigurationsList__Output { + 'scoped_route_configurations': (_envoy_config_route_v3_ScopedRouteConfiguration__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts new file mode 100644 index 000000000..041af534b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts @@ -0,0 +1,273 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../../envoy/config/core/v3/ConfigSource'; +import type { ScopedRouteConfigurationsList as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList, ScopedRouteConfigurationsList__Output as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList'; +import type { ScopedRds as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds, ScopedRds__Output as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds'; + +/** + * Specifies the mechanism for constructing key fragments which are composed into scope keys. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder { + /** + * Specifies how a header field's value should be extracted. + */ + 'header_value_extractor'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor | null); + 'type'?: "header_value_extractor"; +} + +/** + * Specifies the mechanism for constructing key fragments which are composed into scope keys. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder__Output { + /** + * Specifies how a header field's value should be extracted. + */ + 'header_value_extractor'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor__Output | null); + 'type': "header_value_extractor"; +} + +/** + * Specifies how the value of a header should be extracted. + * The following example maps the structure of a header to the fields in this message. + * + * .. code:: + * + * <0> <1> <-- index + * X-Header: a=b;c=d + * | || | + * | || \----> + * | || + * | |\----> + * | | + * | \----> + * | + * \----> + * + * Each 'a=b' key-value pair constitutes an 'element' of the header field. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor { + /** + * The name of the header field to extract the value from. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'name'?: (string); + /** + * The element separator (e.g., ';' separates 'a;b;c;d'). + * Default: empty string. This causes the entirety of the header field to be extracted. + * If this field is set to an empty string and 'index' is used in the oneof below, 'index' + * must be set to 0. + */ + 'element_separator'?: (string); + /** + * Specifies the zero based index of the element to extract. + * Note Envoy concatenates multiple values of the same header key into a comma separated + * string, the splitting always happens after the concatenation. + */ + 'index'?: (number); + /** + * Specifies the key value pair to extract the value from. + */ + 'element'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement | null); + 'extract_type'?: "index"|"element"; +} + +/** + * Specifies how the value of a header should be extracted. + * The following example maps the structure of a header to the fields in this message. + * + * .. code:: + * + * <0> <1> <-- index + * X-Header: a=b;c=d + * | || | + * | || \----> + * | || + * | |\----> + * | | + * | \----> + * | + * \----> + * + * Each 'a=b' key-value pair constitutes an 'element' of the header field. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor__Output { + /** + * The name of the header field to extract the value from. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'name': (string); + /** + * The element separator (e.g., ';' separates 'a;b;c;d'). + * Default: empty string. This causes the entirety of the header field to be extracted. + * If this field is set to an empty string and 'index' is used in the oneof below, 'index' + * must be set to 0. + */ + 'element_separator': (string); + /** + * Specifies the zero based index of the element to extract. + * Note Envoy concatenates multiple values of the same header key into a comma separated + * string, the splitting always happens after the concatenation. + */ + 'index'?: (number); + /** + * Specifies the key value pair to extract the value from. + */ + 'element'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement__Output | null); + 'extract_type': "index"|"element"; +} + +/** + * Specifies a header field's key value pair to match on. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement { + /** + * The separator between key and value (e.g., '=' separates 'k=v;...'). + * If an element is an empty string, the element is ignored. + * If an element contains no separator, the whole element is parsed as key and the + * fragment value is an empty string. + * If there are multiple values for a matched key, the first value is returned. + */ + 'separator'?: (string); + /** + * The key to match on. + */ + 'key'?: (string); +} + +/** + * Specifies a header field's key value pair to match on. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement__Output { + /** + * The separator between key and value (e.g., '=' separates 'k=v;...'). + * If an element is an empty string, the element is ignored. + * If an element contains no separator, the whole element is parsed as key and the + * fragment value is an empty string. + * If there are multiple values for a matched key, the first value is returned. + */ + 'separator': (string); + /** + * The key to match on. + */ + 'key': (string); +} + +/** + * Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These + * keys are matched against a set of :ref:`Key` + * objects assembled from :ref:`ScopedRouteConfiguration` + * messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via + * :ref:`scoped_route_configurations_list`. + * + * Upon receiving a request's headers, the Router will build a key using the algorithm specified + * by this message. This key will be used to look up the routing table (i.e., the + * :ref:`RouteConfiguration`) to use for the request. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder { + /** + * The final(built) scope key consists of the ordered union of these fragments, which are compared in order with the + * fragments of a :ref:`ScopedRouteConfiguration`. + * A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key. + */ + 'fragments'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder)[]; +} + +/** + * Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These + * keys are matched against a set of :ref:`Key` + * objects assembled from :ref:`ScopedRouteConfiguration` + * messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via + * :ref:`scoped_route_configurations_list`. + * + * Upon receiving a request's headers, the Router will build a key using the algorithm specified + * by this message. This key will be used to look up the routing table (i.e., the + * :ref:`RouteConfiguration`) to use for the request. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder__Output { + /** + * The final(built) scope key consists of the ordered union of these fragments, which are compared in order with the + * fragments of a :ref:`ScopedRouteConfiguration`. + * A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key. + */ + 'fragments': (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder__Output)[]; +} + +/** + * [#next-free-field: 6] + */ +export interface ScopedRoutes { + /** + * The name assigned to the scoped routing configuration. + */ + 'name'?: (string); + /** + * The algorithm to use for constructing a scope key for each request. + */ + 'scope_key_builder'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder | null); + /** + * Configuration source specifier for RDS. + * This config source is used to subscribe to RouteConfiguration resources specified in + * ScopedRouteConfiguration messages. + */ + 'rds_config_source'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * The set of routing scopes corresponding to the HCM. A scope is assigned to a request by + * matching a key constructed from the request's attributes according to the algorithm specified + * by the + * :ref:`ScopeKeyBuilder` + * in this message. + */ + 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList | null); + /** + * The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS + * API. A scope is assigned to a request by matching a key constructed from the request's + * attributes according to the algorithm specified by the + * :ref:`ScopeKeyBuilder` + * in this message. + */ + 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds | null); + 'config_specifier'?: "scoped_route_configurations_list"|"scoped_rds"; +} + +/** + * [#next-free-field: 6] + */ +export interface ScopedRoutes__Output { + /** + * The name assigned to the scoped routing configuration. + */ + 'name': (string); + /** + * The algorithm to use for constructing a scope key for each request. + */ + 'scope_key_builder': (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder__Output | null); + /** + * Configuration source specifier for RDS. + * This config source is used to subscribe to RouteConfiguration resources specified in + * ScopedRouteConfiguration messages. + */ + 'rds_config_source': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * The set of routing scopes corresponding to the HCM. A scope is assigned to a request by + * matching a key constructed from the request's attributes according to the algorithm specified + * by the + * :ref:`ScopeKeyBuilder` + * in this message. + */ + 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList__Output | null); + /** + * The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS + * API. A scope is assigned to a request by matching a key constructed from the request's + * attributes according to the algorithm specified by the + * :ref:`ScopeKeyBuilder` + * in this message. + */ + 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds__Output | null); + 'config_specifier': "scoped_route_configurations_list"|"scoped_rds"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts new file mode 100644 index 000000000..3a3100f55 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts @@ -0,0 +1,52 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + + +/** + * Indicates a certificate to be obtained from a named CertificateProvider plugin instance. + * The plugin instances are defined in the client's bootstrap file. + * The plugin allows certificates to be fetched/refreshed over the network asynchronously with + * respect to the TLS handshake. + * [#not-implemented-hide:] + */ +export interface CertificateProviderPluginInstance { + /** + * Provider instance name. If not present, defaults to "default". + * + * Instance names should generally be defined not in terms of the underlying provider + * implementation (e.g., "file_watcher") but rather in terms of the function of the + * certificates (e.g., "foo_deployment_identity"). + */ + 'instance_name'?: (string); + /** + * Opaque name used to specify certificate instances or types. For example, "ROOTCA" to specify + * a root-certificate (validation context) or "example.com" to specify a certificate for a + * particular domain. Not all provider instances will actually use this field, so the value + * defaults to the empty string. + */ + 'certificate_name'?: (string); +} + +/** + * Indicates a certificate to be obtained from a named CertificateProvider plugin instance. + * The plugin instances are defined in the client's bootstrap file. + * The plugin allows certificates to be fetched/refreshed over the network asynchronously with + * respect to the TLS handshake. + * [#not-implemented-hide:] + */ +export interface CertificateProviderPluginInstance__Output { + /** + * Provider instance name. If not present, defaults to "default". + * + * Instance names should generally be defined not in terms of the underlying provider + * implementation (e.g., "file_watcher") but rather in terms of the function of the + * certificates (e.g., "foo_deployment_identity"). + */ + 'instance_name': (string); + /** + * Opaque name used to specify certificate instances or types. For example, "ROOTCA" to specify + * a root-certificate (validation context) or "example.com" to specify a certificate for a + * particular domain. Not all provider instances will actually use this field, so the value + * defaults to the empty string. + */ + 'certificate_name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts new file mode 100644 index 000000000..379320086 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts @@ -0,0 +1,372 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../../google/protobuf/BoolValue'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../../envoy/type/matcher/v3/StringMatcher'; +import type { WatchedDirectory as _envoy_config_core_v3_WatchedDirectory, WatchedDirectory__Output as _envoy_config_core_v3_WatchedDirectory__Output } from '../../../../../envoy/config/core/v3/WatchedDirectory'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { CertificateProviderPluginInstance as _envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance, CertificateProviderPluginInstance__Output as _envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance'; + +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +/** + * Peer certificate verification mode. + */ +export enum _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification { + /** + * Perform default certificate verification (e.g., against CA / verification lists) + */ + VERIFY_TRUST_CHAIN = 0, + /** + * Connections where the certificate fails verification will be permitted. + * For HTTP connections, the result of certificate verification can be used in route matching. ( + * see :ref:`validated ` ). + */ + ACCEPT_UNTRUSTED = 1, +} + +/** + * [#next-free-field: 14] + */ +export interface CertificateValidationContext { + /** + * TLS certificate data containing certificate authority certificates to use in verifying + * a presented peer certificate (e.g. server certificate for clusters or client certificate + * for listeners). If not specified and a peer certificate is presented it will not be + * verified. By default, a client certificate is optional, unless one of the additional + * options (:ref:`require_client_certificate + * `, + * :ref:`verify_certificate_spki + * `, + * :ref:`verify_certificate_hash + * `, or + * :ref:`match_subject_alt_names + * `) is also + * specified. + * + * It can optionally contain certificate revocation lists, in which case Envoy will verify + * that the presented peer certificate has not been revoked by one of the included CRLs. Note + * that if a CRL is provided for any certificate authority in a trust chain, a CRL must be + * provided for all certificate authorities in that chain. Failure to do so will result in + * verification failure for both revoked and unrevoked certificates from that chain. + * + * See :ref:`the TLS overview ` for a list of common + * system CA locations. + * + * If *trusted_ca* is a filesystem path, a watch will be added to the parent + * directory for any file moves to support rotation. This currently only + * applies to dynamic secrets, when the *CertificateValidationContext* is + * delivered via SDS. + * + * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. + * + * [#next-major-version: This field and watched_directory below should ideally be moved into a + * separate sub-message, since there's no point in specifying the latter field without this one.] + */ + 'trusted_ca'?: (_envoy_config_core_v3_DataSource | null); + /** + * An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that + * the SHA-256 of the DER-encoded presented certificate matches one of the specified values. + * + * A hex-encoded SHA-256 of the certificate can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 + * df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a + * + * A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate + * can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 + * DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A + * + * Both of those formats are acceptable. + * + * When both: + * :ref:`verify_certificate_hash + * ` and + * :ref:`verify_certificate_spki + * ` are specified, + * a hash matching value from either of the lists will result in the certificate being accepted. + */ + 'verify_certificate_hash'?: (string)[]; + /** + * An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the + * SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate + * matches one of the specified values. + * + * A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate + * can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -noout -pubkey + * | openssl pkey -pubin -outform DER + * | openssl dgst -sha256 -binary + * | openssl enc -base64 + * NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= + * + * This is the format used in HTTP Public Key Pinning. + * + * When both: + * :ref:`verify_certificate_hash + * ` and + * :ref:`verify_certificate_spki + * ` are specified, + * a hash matching value from either of the lists will result in the certificate being accepted. + * + * .. attention:: + * + * This option is preferred over :ref:`verify_certificate_hash + * `, + * because SPKI is tied to a private key, so it doesn't change when the certificate + * is renewed using the same private key. + */ + 'verify_certificate_spki'?: (string)[]; + /** + * [#not-implemented-hide:] Must present signed certificate time-stamp. + */ + 'require_signed_certificate_timestamp'?: (_google_protobuf_BoolValue | null); + /** + * An optional `certificate revocation list + * `_ + * (in PEM format). If specified, Envoy will verify that the presented peer + * certificate has not been revoked by this CRL. If this DataSource contains + * multiple CRLs, all of them will be used. Note that if a CRL is provided + * for any certificate authority in a trust chain, a CRL must be provided + * for all certificate authorities in that chain. Failure to do so will + * result in verification failure for both revoked and unrevoked certificates + * from that chain. + */ + 'crl'?: (_envoy_config_core_v3_DataSource | null); + /** + * If specified, Envoy will not reject expired certificates. + */ + 'allow_expired_certificate'?: (boolean); + /** + * An optional list of Subject Alternative name matchers. If specified, Envoy will verify that the + * Subject Alternative Name of the presented certificate matches one of the specified matchers. + * + * When a certificate has wildcard DNS SAN entries, to match a specific client, it should be + * configured with exact match type in the :ref:`string matcher `. + * For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", + * it should be configured as shown below. + * + * .. code-block:: yaml + * + * match_subject_alt_names: + * exact: "api.example.com" + * + * .. attention:: + * + * Subject Alternative Names are easily spoofable and verifying only them is insecure, + * therefore this option must be used together with :ref:`trusted_ca + * `. + */ + 'match_subject_alt_names'?: (_envoy_type_matcher_v3_StringMatcher)[]; + /** + * Certificate trust chain verification mode. + */ + 'trust_chain_verification'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification | keyof typeof _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification); + /** + * If specified, updates of a file-based *trusted_ca* source will be triggered + * by this watch. This allows explicit control over the path watched, by + * default the parent directory of the filesystem path in *trusted_ca* is + * watched if this field is not specified. This only applies when a + * *CertificateValidationContext* is delivered by SDS with references to + * filesystem paths. See the :ref:`SDS key rotation ` + * documentation for further details. + */ + 'watched_directory'?: (_envoy_config_core_v3_WatchedDirectory | null); + /** + * The configuration of an extension specific certificate validator. + * If specified, all validation is done by the specified validator, + * and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). + * Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. + * [#extension-category: envoy.tls.cert_validator] + */ + 'custom_validator_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Certificate provider instance for fetching TLS certificates. + * + * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. + * [#not-implemented-hide:] + */ + 'ca_certificate_provider_instance'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance | null); +} + +/** + * [#next-free-field: 14] + */ +export interface CertificateValidationContext__Output { + /** + * TLS certificate data containing certificate authority certificates to use in verifying + * a presented peer certificate (e.g. server certificate for clusters or client certificate + * for listeners). If not specified and a peer certificate is presented it will not be + * verified. By default, a client certificate is optional, unless one of the additional + * options (:ref:`require_client_certificate + * `, + * :ref:`verify_certificate_spki + * `, + * :ref:`verify_certificate_hash + * `, or + * :ref:`match_subject_alt_names + * `) is also + * specified. + * + * It can optionally contain certificate revocation lists, in which case Envoy will verify + * that the presented peer certificate has not been revoked by one of the included CRLs. Note + * that if a CRL is provided for any certificate authority in a trust chain, a CRL must be + * provided for all certificate authorities in that chain. Failure to do so will result in + * verification failure for both revoked and unrevoked certificates from that chain. + * + * See :ref:`the TLS overview ` for a list of common + * system CA locations. + * + * If *trusted_ca* is a filesystem path, a watch will be added to the parent + * directory for any file moves to support rotation. This currently only + * applies to dynamic secrets, when the *CertificateValidationContext* is + * delivered via SDS. + * + * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. + * + * [#next-major-version: This field and watched_directory below should ideally be moved into a + * separate sub-message, since there's no point in specifying the latter field without this one.] + */ + 'trusted_ca': (_envoy_config_core_v3_DataSource__Output | null); + /** + * An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that + * the SHA-256 of the DER-encoded presented certificate matches one of the specified values. + * + * A hex-encoded SHA-256 of the certificate can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 + * df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a + * + * A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate + * can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 + * DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A + * + * Both of those formats are acceptable. + * + * When both: + * :ref:`verify_certificate_hash + * ` and + * :ref:`verify_certificate_spki + * ` are specified, + * a hash matching value from either of the lists will result in the certificate being accepted. + */ + 'verify_certificate_hash': (string)[]; + /** + * An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the + * SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate + * matches one of the specified values. + * + * A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate + * can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -noout -pubkey + * | openssl pkey -pubin -outform DER + * | openssl dgst -sha256 -binary + * | openssl enc -base64 + * NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= + * + * This is the format used in HTTP Public Key Pinning. + * + * When both: + * :ref:`verify_certificate_hash + * ` and + * :ref:`verify_certificate_spki + * ` are specified, + * a hash matching value from either of the lists will result in the certificate being accepted. + * + * .. attention:: + * + * This option is preferred over :ref:`verify_certificate_hash + * `, + * because SPKI is tied to a private key, so it doesn't change when the certificate + * is renewed using the same private key. + */ + 'verify_certificate_spki': (string)[]; + /** + * [#not-implemented-hide:] Must present signed certificate time-stamp. + */ + 'require_signed_certificate_timestamp': (_google_protobuf_BoolValue__Output | null); + /** + * An optional `certificate revocation list + * `_ + * (in PEM format). If specified, Envoy will verify that the presented peer + * certificate has not been revoked by this CRL. If this DataSource contains + * multiple CRLs, all of them will be used. Note that if a CRL is provided + * for any certificate authority in a trust chain, a CRL must be provided + * for all certificate authorities in that chain. Failure to do so will + * result in verification failure for both revoked and unrevoked certificates + * from that chain. + */ + 'crl': (_envoy_config_core_v3_DataSource__Output | null); + /** + * If specified, Envoy will not reject expired certificates. + */ + 'allow_expired_certificate': (boolean); + /** + * An optional list of Subject Alternative name matchers. If specified, Envoy will verify that the + * Subject Alternative Name of the presented certificate matches one of the specified matchers. + * + * When a certificate has wildcard DNS SAN entries, to match a specific client, it should be + * configured with exact match type in the :ref:`string matcher `. + * For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", + * it should be configured as shown below. + * + * .. code-block:: yaml + * + * match_subject_alt_names: + * exact: "api.example.com" + * + * .. attention:: + * + * Subject Alternative Names are easily spoofable and verifying only them is insecure, + * therefore this option must be used together with :ref:`trusted_ca + * `. + */ + 'match_subject_alt_names': (_envoy_type_matcher_v3_StringMatcher__Output)[]; + /** + * Certificate trust chain verification mode. + */ + 'trust_chain_verification': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification); + /** + * If specified, updates of a file-based *trusted_ca* source will be triggered + * by this watch. This allows explicit control over the path watched, by + * default the parent directory of the filesystem path in *trusted_ca* is + * watched if this field is not specified. This only applies when a + * *CertificateValidationContext* is delivered by SDS with references to + * filesystem paths. See the :ref:`SDS key rotation ` + * documentation for further details. + */ + 'watched_directory': (_envoy_config_core_v3_WatchedDirectory__Output | null); + /** + * The configuration of an extension specific certificate validator. + * If specified, all validation is done by the specified validator, + * and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). + * Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. + * [#extension-category: envoy.tls.cert_validator] + */ + 'custom_validator_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Certificate provider instance for fetching TLS certificates. + * + * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. + * [#not-implemented-hide:] + */ + 'ca_certificate_provider_instance': (_envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts new file mode 100644 index 000000000..b206fb13a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; + +export interface GenericSecret { + /** + * Secret of generic type and is available to filters. + */ + 'secret'?: (_envoy_config_core_v3_DataSource | null); +} + +export interface GenericSecret__Output { + /** + * Secret of generic type and is available to filters. + */ + 'secret': (_envoy_config_core_v3_DataSource__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts new file mode 100644 index 000000000..b4a2ad933 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts @@ -0,0 +1,39 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../google/protobuf/Any'; + +/** + * BoringSSL private key method configuration. The private key methods are used for external + * (potentially asynchronous) signing and decryption operations. Some use cases for private key + * methods would be TPM support and TLS acceleration. + */ +export interface PrivateKeyProvider { + /** + * Private key method provider name. The name must match a + * supported private key method provider type. + */ + 'provider_name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Private key method provider specific configuration. + */ + 'config_type'?: "typed_config"; +} + +/** + * BoringSSL private key method configuration. The private key methods are used for external + * (potentially asynchronous) signing and decryption operations. Some use cases for private key + * methods would be TPM support and TLS acceleration. + */ +export interface PrivateKeyProvider__Output { + /** + * Private key method provider name. The name must match a + * supported private key method provider type. + */ + 'provider_name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Private key method provider specific configuration. + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts new file mode 100644 index 000000000..38b850c50 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts @@ -0,0 +1,23 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../envoy/config/core/v3/ConfigSource'; + +export interface SdsSecretConfig { + /** + * Name by which the secret can be uniquely referred to. When both name and config are specified, + * then secret can be fetched and/or reloaded via SDS. When only name is specified, then secret + * will be loaded from static resources. + */ + 'name'?: (string); + 'sds_config'?: (_envoy_config_core_v3_ConfigSource | null); +} + +export interface SdsSecretConfig__Output { + /** + * Name by which the secret can be uniquely referred to. When both name and config are specified, + * then secret can be fetched and/or reloaded via SDS. When only name is specified, then secret + * will be loaded from static resources. + */ + 'name': (string); + 'sds_config': (_envoy_config_core_v3_ConfigSource__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts new file mode 100644 index 000000000..c86957da5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto + +import type { TlsCertificate as _envoy_extensions_transport_sockets_tls_v3_TlsCertificate, TlsCertificate__Output as _envoy_extensions_transport_sockets_tls_v3_TlsCertificate__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/TlsCertificate'; +import type { TlsSessionTicketKeys as _envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys, TlsSessionTicketKeys__Output as _envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys'; +import type { CertificateValidationContext as _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext, CertificateValidationContext__Output as _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext'; +import type { GenericSecret as _envoy_extensions_transport_sockets_tls_v3_GenericSecret, GenericSecret__Output as _envoy_extensions_transport_sockets_tls_v3_GenericSecret__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/GenericSecret'; + +/** + * [#next-free-field: 6] + */ +export interface Secret { + /** + * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. + */ + 'name'?: (string); + 'tls_certificate'?: (_envoy_extensions_transport_sockets_tls_v3_TlsCertificate | null); + 'session_ticket_keys'?: (_envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys | null); + 'validation_context'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext | null); + 'generic_secret'?: (_envoy_extensions_transport_sockets_tls_v3_GenericSecret | null); + 'type'?: "tls_certificate"|"session_ticket_keys"|"validation_context"|"generic_secret"; +} + +/** + * [#next-free-field: 6] + */ +export interface Secret__Output { + /** + * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. + */ + 'name': (string); + 'tls_certificate'?: (_envoy_extensions_transport_sockets_tls_v3_TlsCertificate__Output | null); + 'session_ticket_keys'?: (_envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys__Output | null); + 'validation_context'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext__Output | null); + 'generic_secret'?: (_envoy_extensions_transport_sockets_tls_v3_GenericSecret__Output | null); + 'type': "tls_certificate"|"session_ticket_keys"|"validation_context"|"generic_secret"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts new file mode 100644 index 000000000..ce8046e95 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts @@ -0,0 +1,127 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; +import type { PrivateKeyProvider as _envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider, PrivateKeyProvider__Output as _envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider'; +import type { WatchedDirectory as _envoy_config_core_v3_WatchedDirectory, WatchedDirectory__Output as _envoy_config_core_v3_WatchedDirectory__Output } from '../../../../../envoy/config/core/v3/WatchedDirectory'; + +/** + * [#next-free-field: 8] + */ +export interface TlsCertificate { + /** + * The TLS certificate chain. + * + * If *certificate_chain* is a filesystem path, a watch will be added to the + * parent directory for any file moves to support rotation. This currently + * only applies to dynamic secrets, when the *TlsCertificate* is delivered via + * SDS. + */ + 'certificate_chain'?: (_envoy_config_core_v3_DataSource | null); + /** + * The TLS private key. + * + * If *private_key* is a filesystem path, a watch will be added to the parent + * directory for any file moves to support rotation. This currently only + * applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. + */ + 'private_key'?: (_envoy_config_core_v3_DataSource | null); + /** + * The password to decrypt the TLS private key. If this field is not set, it is assumed that the + * TLS private key is not password encrypted. + */ + 'password'?: (_envoy_config_core_v3_DataSource | null); + /** + * The OCSP response to be stapled with this certificate during the handshake. + * The response must be DER-encoded and may only be provided via ``filename`` or + * ``inline_bytes``. The response may pertain to only one certificate. + */ + 'ocsp_staple'?: (_envoy_config_core_v3_DataSource | null); + /** + * [#not-implemented-hide:] + */ + 'signed_certificate_timestamp'?: (_envoy_config_core_v3_DataSource)[]; + /** + * BoringSSL private key method provider. This is an alternative to :ref:`private_key + * ` field. This can't be + * marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key + * ` and + * :ref:`private_key_provider + * ` fields will result in an + * error. + */ + 'private_key_provider'?: (_envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider | null); + /** + * If specified, updates of file-based *certificate_chain* and *private_key* + * sources will be triggered by this watch. The certificate/key pair will be + * read together and validated for atomic read consistency (i.e. no + * intervening modification occurred between cert/key read, verified by file + * hash comparisons). This allows explicit control over the path watched, by + * default the parent directories of the filesystem paths in + * *certificate_chain* and *private_key* are watched if this field is not + * specified. This only applies when a *TlsCertificate* is delivered by SDS + * with references to filesystem paths. See the :ref:`SDS key rotation + * ` documentation for further details. + */ + 'watched_directory'?: (_envoy_config_core_v3_WatchedDirectory | null); +} + +/** + * [#next-free-field: 8] + */ +export interface TlsCertificate__Output { + /** + * The TLS certificate chain. + * + * If *certificate_chain* is a filesystem path, a watch will be added to the + * parent directory for any file moves to support rotation. This currently + * only applies to dynamic secrets, when the *TlsCertificate* is delivered via + * SDS. + */ + 'certificate_chain': (_envoy_config_core_v3_DataSource__Output | null); + /** + * The TLS private key. + * + * If *private_key* is a filesystem path, a watch will be added to the parent + * directory for any file moves to support rotation. This currently only + * applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. + */ + 'private_key': (_envoy_config_core_v3_DataSource__Output | null); + /** + * The password to decrypt the TLS private key. If this field is not set, it is assumed that the + * TLS private key is not password encrypted. + */ + 'password': (_envoy_config_core_v3_DataSource__Output | null); + /** + * The OCSP response to be stapled with this certificate during the handshake. + * The response must be DER-encoded and may only be provided via ``filename`` or + * ``inline_bytes``. The response may pertain to only one certificate. + */ + 'ocsp_staple': (_envoy_config_core_v3_DataSource__Output | null); + /** + * [#not-implemented-hide:] + */ + 'signed_certificate_timestamp': (_envoy_config_core_v3_DataSource__Output)[]; + /** + * BoringSSL private key method provider. This is an alternative to :ref:`private_key + * ` field. This can't be + * marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key + * ` and + * :ref:`private_key_provider + * ` fields will result in an + * error. + */ + 'private_key_provider': (_envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider__Output | null); + /** + * If specified, updates of file-based *certificate_chain* and *private_key* + * sources will be triggered by this watch. The certificate/key pair will be + * read together and validated for atomic read consistency (i.e. no + * intervening modification occurred between cert/key read, verified by file + * hash comparisons). This allows explicit control over the path watched, by + * default the parent directories of the filesystem paths in + * *certificate_chain* and *private_key* are watched if this field is not + * specified. This only applies when a *TlsCertificate* is delivered by SDS + * with references to filesystem paths. See the :ref:`SDS key rotation + * ` documentation for further details. + */ + 'watched_directory': (_envoy_config_core_v3_WatchedDirectory__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts new file mode 100644 index 000000000..e68464c8b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts @@ -0,0 +1,211 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + + +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +export enum _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol { + /** + * Envoy will choose the optimal TLS version. + */ + TLS_AUTO = 0, + /** + * TLS 1.0 + */ + TLSv1_0 = 1, + /** + * TLS 1.1 + */ + TLSv1_1 = 2, + /** + * TLS 1.2 + */ + TLSv1_2 = 3, + /** + * TLS 1.3 + */ + TLSv1_3 = 4, +} + +export interface TlsParameters { + /** + * Minimum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_0`` for + * servers. + */ + 'tls_minimum_protocol_version'?: (_envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol | keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); + /** + * Maximum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_3`` for + * servers. + */ + 'tls_maximum_protocol_version'?: (_envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol | keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); + /** + * If specified, the TLS listener will only support the specified `cipher list + * `_ + * when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). + * + * If not specified, a default list will be used. Defaults are different for server (downstream) and + * client (upstream) TLS configurations. + * + * In non-FIPS builds, the default server cipher list is: + * + * .. code-block:: none + * + * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + * ECDHE-ECDSA-AES128-SHA + * ECDHE-RSA-AES128-SHA + * AES128-GCM-SHA256 + * AES128-SHA + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * ECDHE-ECDSA-AES256-SHA + * ECDHE-RSA-AES256-SHA + * AES256-GCM-SHA384 + * AES256-SHA + * + * In builds using :ref:`BoringSSL FIPS `, the default server cipher list is: + * + * .. code-block:: none + * + * ECDHE-ECDSA-AES128-GCM-SHA256 + * ECDHE-RSA-AES128-GCM-SHA256 + * ECDHE-ECDSA-AES128-SHA + * ECDHE-RSA-AES128-SHA + * AES128-GCM-SHA256 + * AES128-SHA + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * ECDHE-ECDSA-AES256-SHA + * ECDHE-RSA-AES256-SHA + * AES256-GCM-SHA384 + * AES256-SHA + * + * In non-FIPS builds, the default client cipher list is: + * + * .. code-block:: none + * + * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * + * In builds using :ref:`BoringSSL FIPS `, the default client cipher list is: + * + * .. code-block:: none + * + * ECDHE-ECDSA-AES128-GCM-SHA256 + * ECDHE-RSA-AES128-GCM-SHA256 + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + */ + 'cipher_suites'?: (string)[]; + /** + * If specified, the TLS connection will only support the specified ECDH + * curves. If not specified, the default curves will be used. + * + * In non-FIPS builds, the default curves are: + * + * .. code-block:: none + * + * X25519 + * P-256 + * + * In builds using :ref:`BoringSSL FIPS `, the default curve is: + * + * .. code-block:: none + * + * P-256 + */ + 'ecdh_curves'?: (string)[]; +} + +export interface TlsParameters__Output { + /** + * Minimum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_0`` for + * servers. + */ + 'tls_minimum_protocol_version': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); + /** + * Maximum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_3`` for + * servers. + */ + 'tls_maximum_protocol_version': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); + /** + * If specified, the TLS listener will only support the specified `cipher list + * `_ + * when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). + * + * If not specified, a default list will be used. Defaults are different for server (downstream) and + * client (upstream) TLS configurations. + * + * In non-FIPS builds, the default server cipher list is: + * + * .. code-block:: none + * + * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + * ECDHE-ECDSA-AES128-SHA + * ECDHE-RSA-AES128-SHA + * AES128-GCM-SHA256 + * AES128-SHA + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * ECDHE-ECDSA-AES256-SHA + * ECDHE-RSA-AES256-SHA + * AES256-GCM-SHA384 + * AES256-SHA + * + * In builds using :ref:`BoringSSL FIPS `, the default server cipher list is: + * + * .. code-block:: none + * + * ECDHE-ECDSA-AES128-GCM-SHA256 + * ECDHE-RSA-AES128-GCM-SHA256 + * ECDHE-ECDSA-AES128-SHA + * ECDHE-RSA-AES128-SHA + * AES128-GCM-SHA256 + * AES128-SHA + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * ECDHE-ECDSA-AES256-SHA + * ECDHE-RSA-AES256-SHA + * AES256-GCM-SHA384 + * AES256-SHA + * + * In non-FIPS builds, the default client cipher list is: + * + * .. code-block:: none + * + * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * + * In builds using :ref:`BoringSSL FIPS `, the default client cipher list is: + * + * .. code-block:: none + * + * ECDHE-ECDSA-AES128-GCM-SHA256 + * ECDHE-RSA-AES128-GCM-SHA256 + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + */ + 'cipher_suites': (string)[]; + /** + * If specified, the TLS connection will only support the specified ECDH + * curves. If not specified, the default curves will be used. + * + * In non-FIPS builds, the default curves are: + * + * .. code-block:: none + * + * X25519 + * P-256 + * + * In builds using :ref:`BoringSSL FIPS `, the default curve is: + * + * .. code-block:: none + * + * P-256 + */ + 'ecdh_curves': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts new file mode 100644 index 000000000..152bccac7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts @@ -0,0 +1,61 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; + +export interface TlsSessionTicketKeys { + /** + * Keys for encrypting and decrypting TLS session tickets. The + * first key in the array contains the key to encrypt all new sessions created by this context. + * All keys are candidates for decrypting received tickets. This allows for easy rotation of keys + * by, for example, putting the new key first, and the previous key second. + * + * If :ref:`session_ticket_keys ` + * is not specified, the TLS library will still support resuming sessions via tickets, but it will + * use an internally-generated and managed key, so sessions cannot be resumed across hot restarts + * or on different hosts. + * + * Each key must contain exactly 80 bytes of cryptographically-secure random data. For + * example, the output of ``openssl rand 80``. + * + * .. attention:: + * + * Using this feature has serious security considerations and risks. Improper handling of keys + * may result in loss of secrecy in connections, even if ciphers supporting perfect forward + * secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some + * discussion. To minimize the risk, you must: + * + * * Keep the session ticket keys at least as secure as your TLS certificate private keys + * * Rotate session ticket keys at least daily, and preferably hourly + * * Always generate keys using a cryptographically-secure random data source + */ + 'keys'?: (_envoy_config_core_v3_DataSource)[]; +} + +export interface TlsSessionTicketKeys__Output { + /** + * Keys for encrypting and decrypting TLS session tickets. The + * first key in the array contains the key to encrypt all new sessions created by this context. + * All keys are candidates for decrypting received tickets. This allows for easy rotation of keys + * by, for example, putting the new key first, and the previous key second. + * + * If :ref:`session_ticket_keys ` + * is not specified, the TLS library will still support resuming sessions via tickets, but it will + * use an internally-generated and managed key, so sessions cannot be resumed across hot restarts + * or on different hosts. + * + * Each key must contain exactly 80 bytes of cryptographically-secure random data. For + * example, the output of ``openssl rand 80``. + * + * .. attention:: + * + * Using this feature has serious security considerations and risks. Improper handling of keys + * may result in loss of secrecy in connections, even if ciphers supporting perfect forward + * secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some + * discussion. To minimize the risk, you must: + * + * * Keep the session ticket keys at least as secure as your TLS certificate private keys + * * Rotate session ticket keys at least daily, and preferably hourly + * * Always generate keys using a cryptographically-secure random data source + */ + 'keys': (_envoy_config_core_v3_DataSource__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AdsDummy.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AdsDummy.ts new file mode 100644 index 000000000..c15510877 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AdsDummy.ts @@ -0,0 +1,16 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/ads.proto + + +/** + * [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing + * services: https://github.com/google/protobuf/issues/4221 + */ +export interface AdsDummy { +} + +/** + * [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing + * services: https://github.com/google/protobuf/issues/4221 + */ +export interface AdsDummy__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts new file mode 100644 index 000000000..e6498177b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts @@ -0,0 +1,58 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/ads.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { DeltaDiscoveryRequest as _envoy_service_discovery_v3_DeltaDiscoveryRequest, DeltaDiscoveryRequest__Output as _envoy_service_discovery_v3_DeltaDiscoveryRequest__Output } from '../../../../envoy/service/discovery/v3/DeltaDiscoveryRequest'; +import type { DeltaDiscoveryResponse as _envoy_service_discovery_v3_DeltaDiscoveryResponse, DeltaDiscoveryResponse__Output as _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output } from '../../../../envoy/service/discovery/v3/DeltaDiscoveryResponse'; +import type { DiscoveryRequest as _envoy_service_discovery_v3_DiscoveryRequest, DiscoveryRequest__Output as _envoy_service_discovery_v3_DiscoveryRequest__Output } from '../../../../envoy/service/discovery/v3/DiscoveryRequest'; +import type { DiscoveryResponse as _envoy_service_discovery_v3_DiscoveryResponse, DiscoveryResponse__Output as _envoy_service_discovery_v3_DiscoveryResponse__Output } from '../../../../envoy/service/discovery/v3/DiscoveryResponse'; + +/** + * See https://github.com/lyft/envoy-api#apis for a description of the role of + * ADS and how it is intended to be used by a management server. ADS requests + * have the same structure as their singleton xDS counterparts, but can + * multiplex many resource types on a single stream. The type_url in the + * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover + * the multiplexed singleton APIs at the Envoy instance and management server. + */ +export interface AggregatedDiscoveryServiceClient extends grpc.Client { + DeltaAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output>; + DeltaAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output>; + deltaAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output>; + deltaAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output>; + + /** + * This is a gRPC-only API. + */ + StreamAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse__Output>; + StreamAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse__Output>; + /** + * This is a gRPC-only API. + */ + streamAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse__Output>; + streamAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse__Output>; + +} + +/** + * See https://github.com/lyft/envoy-api#apis for a description of the role of + * ADS and how it is intended to be used by a management server. ADS requests + * have the same structure as their singleton xDS counterparts, but can + * multiplex many resource types on a single stream. The type_url in the + * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover + * the multiplexed singleton APIs at the Envoy instance and management server. + */ +export interface AggregatedDiscoveryServiceHandlers extends grpc.UntypedServiceImplementation { + DeltaAggregatedResources: grpc.handleBidiStreamingCall<_envoy_service_discovery_v3_DeltaDiscoveryRequest__Output, _envoy_service_discovery_v3_DeltaDiscoveryResponse>; + + /** + * This is a gRPC-only API. + */ + StreamAggregatedResources: grpc.handleBidiStreamingCall<_envoy_service_discovery_v3_DiscoveryRequest__Output, _envoy_service_discovery_v3_DiscoveryResponse>; + +} + +export interface AggregatedDiscoveryServiceDefinition extends grpc.ServiceDefinition { + DeltaAggregatedResources: MethodDefinition<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse, _envoy_service_discovery_v3_DeltaDiscoveryRequest__Output, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output> + StreamAggregatedResources: MethodDefinition<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse, _envoy_service_discovery_v3_DiscoveryRequest__Output, _envoy_service_discovery_v3_DiscoveryResponse__Output> +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts new file mode 100644 index 000000000..6e900970a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts @@ -0,0 +1,206 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../../google/rpc/Status'; + +/** + * DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC + * endpoint for Delta xDS. + * + * With Delta xDS, the DeltaDiscoveryResponses do not need to include a full + * snapshot of the tracked resources. Instead, DeltaDiscoveryResponses are a + * diff to the state of a xDS client. + * In Delta XDS there are per-resource versions, which allow tracking state at + * the resource granularity. + * An xDS Delta session is always in the context of a gRPC bidirectional + * stream. This allows the xDS server to keep track of the state of xDS clients + * connected to it. + * + * In Delta xDS the nonce field is required and used to pair + * DeltaDiscoveryResponse to a DeltaDiscoveryRequest ACK or NACK. + * Optionally, a response message level system_version_info is present for + * debugging purposes only. + * + * DeltaDiscoveryRequest plays two independent roles. Any DeltaDiscoveryRequest + * can be either or both of: [1] informing the server of what resources the + * client has gained/lost interest in (using resource_names_subscribe and + * resource_names_unsubscribe), or [2] (N)ACKing an earlier resource update from + * the server (using response_nonce, with presence of error_detail making it a NACK). + * Additionally, the first message (for a given type_url) of a reconnected gRPC stream + * has a third role: informing the server of the resources (and their versions) + * that the client already possesses, using the initial_resource_versions field. + * + * As with state-of-the-world, when multiple resource types are multiplexed (ADS), + * all requests/acknowledgments/updates are logically walled off by type_url: + * a Cluster ACK exists in a completely separate world from a prior Route NACK. + * In particular, initial_resource_versions being sent at the "start" of every + * gRPC stream actually entails a message for each type_url, each with its own + * initial_resource_versions. + * [#next-free-field: 8] + */ +export interface DeltaDiscoveryRequest { + /** + * The node making the request. + */ + 'node'?: (_envoy_config_core_v3_Node | null); + /** + * Type of the resource that is being requested, e.g. + * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if + * resources are only referenced via *xds_resource_subscribe* and + * *xds_resources_unsubscribe*. + */ + 'type_url'?: (string); + /** + * DeltaDiscoveryRequests allow the client to add or remove individual + * resources to the set of tracked resources in the context of a stream. + * All resource names in the resource_names_subscribe list are added to the + * set of tracked resources and all resource names in the resource_names_unsubscribe + * list are removed from the set of tracked resources. + * + * *Unlike* state-of-the-world xDS, an empty resource_names_subscribe or + * resource_names_unsubscribe list simply means that no resources are to be + * added or removed to the resource list. + * *Like* state-of-the-world xDS, the server must send updates for all tracked + * resources, but can also send updates for resources the client has not subscribed to. + * + * NOTE: the server must respond with all resources listed in resource_names_subscribe, + * even if it believes the client has the most recent version of them. The reason: + * the client may have dropped them, but then regained interest before it had a chance + * to send the unsubscribe message. See DeltaSubscriptionStateTest.RemoveThenAdd. + * + * These two fields can be set in any DeltaDiscoveryRequest, including ACKs + * and initial_resource_versions. + * + * A list of Resource names to add to the list of tracked resources. + */ + 'resource_names_subscribe'?: (string)[]; + /** + * A list of Resource names to remove from the list of tracked resources. + */ + 'resource_names_unsubscribe'?: (string)[]; + /** + * Informs the server of the versions of the resources the xDS client knows of, to enable the + * client to continue the same logical xDS session even in the face of gRPC stream reconnection. + * It will not be populated: [1] in the very first stream of a session, since the client will + * not yet have any resources, [2] in any message after the first in a stream (for a given + * type_url), since the server will already be correctly tracking the client's state. + * (In ADS, the first message *of each type_url* of a reconnected stream populates this map.) + * The map's keys are names of xDS resources known to the xDS client. + * The map's values are opaque resource versions. + */ + 'initial_resource_versions'?: ({[key: string]: string}); + /** + * When the DeltaDiscoveryRequest is a ACK or NACK message in response + * to a previous DeltaDiscoveryResponse, the response_nonce must be the + * nonce in the DeltaDiscoveryResponse. + * Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. + */ + 'response_nonce'?: (string); + /** + * This is populated when the previous :ref:`DiscoveryResponse ` + * failed to update configuration. The *message* field in *error_details* + * provides the Envoy internal exception related to the failure. + */ + 'error_detail'?: (_google_rpc_Status | null); +} + +/** + * DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC + * endpoint for Delta xDS. + * + * With Delta xDS, the DeltaDiscoveryResponses do not need to include a full + * snapshot of the tracked resources. Instead, DeltaDiscoveryResponses are a + * diff to the state of a xDS client. + * In Delta XDS there are per-resource versions, which allow tracking state at + * the resource granularity. + * An xDS Delta session is always in the context of a gRPC bidirectional + * stream. This allows the xDS server to keep track of the state of xDS clients + * connected to it. + * + * In Delta xDS the nonce field is required and used to pair + * DeltaDiscoveryResponse to a DeltaDiscoveryRequest ACK or NACK. + * Optionally, a response message level system_version_info is present for + * debugging purposes only. + * + * DeltaDiscoveryRequest plays two independent roles. Any DeltaDiscoveryRequest + * can be either or both of: [1] informing the server of what resources the + * client has gained/lost interest in (using resource_names_subscribe and + * resource_names_unsubscribe), or [2] (N)ACKing an earlier resource update from + * the server (using response_nonce, with presence of error_detail making it a NACK). + * Additionally, the first message (for a given type_url) of a reconnected gRPC stream + * has a third role: informing the server of the resources (and their versions) + * that the client already possesses, using the initial_resource_versions field. + * + * As with state-of-the-world, when multiple resource types are multiplexed (ADS), + * all requests/acknowledgments/updates are logically walled off by type_url: + * a Cluster ACK exists in a completely separate world from a prior Route NACK. + * In particular, initial_resource_versions being sent at the "start" of every + * gRPC stream actually entails a message for each type_url, each with its own + * initial_resource_versions. + * [#next-free-field: 8] + */ +export interface DeltaDiscoveryRequest__Output { + /** + * The node making the request. + */ + 'node': (_envoy_config_core_v3_Node__Output | null); + /** + * Type of the resource that is being requested, e.g. + * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if + * resources are only referenced via *xds_resource_subscribe* and + * *xds_resources_unsubscribe*. + */ + 'type_url': (string); + /** + * DeltaDiscoveryRequests allow the client to add or remove individual + * resources to the set of tracked resources in the context of a stream. + * All resource names in the resource_names_subscribe list are added to the + * set of tracked resources and all resource names in the resource_names_unsubscribe + * list are removed from the set of tracked resources. + * + * *Unlike* state-of-the-world xDS, an empty resource_names_subscribe or + * resource_names_unsubscribe list simply means that no resources are to be + * added or removed to the resource list. + * *Like* state-of-the-world xDS, the server must send updates for all tracked + * resources, but can also send updates for resources the client has not subscribed to. + * + * NOTE: the server must respond with all resources listed in resource_names_subscribe, + * even if it believes the client has the most recent version of them. The reason: + * the client may have dropped them, but then regained interest before it had a chance + * to send the unsubscribe message. See DeltaSubscriptionStateTest.RemoveThenAdd. + * + * These two fields can be set in any DeltaDiscoveryRequest, including ACKs + * and initial_resource_versions. + * + * A list of Resource names to add to the list of tracked resources. + */ + 'resource_names_subscribe': (string)[]; + /** + * A list of Resource names to remove from the list of tracked resources. + */ + 'resource_names_unsubscribe': (string)[]; + /** + * Informs the server of the versions of the resources the xDS client knows of, to enable the + * client to continue the same logical xDS session even in the face of gRPC stream reconnection. + * It will not be populated: [1] in the very first stream of a session, since the client will + * not yet have any resources, [2] in any message after the first in a stream (for a given + * type_url), since the server will already be correctly tracking the client's state. + * (In ADS, the first message *of each type_url* of a reconnected stream populates this map.) + * The map's keys are names of xDS resources known to the xDS client. + * The map's values are opaque resource versions. + */ + 'initial_resource_versions': ({[key: string]: string}); + /** + * When the DeltaDiscoveryRequest is a ACK or NACK message in response + * to a previous DeltaDiscoveryResponse, the response_nonce must be the + * nonce in the DeltaDiscoveryResponse. + * Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. + */ + 'response_nonce': (string); + /** + * This is populated when the previous :ref:`DiscoveryResponse ` + * failed to update configuration. The *message* field in *error_details* + * provides the Envoy internal exception related to the failure. + */ + 'error_detail': (_google_rpc_Status__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts new file mode 100644 index 000000000..efbbed85c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts @@ -0,0 +1,74 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Resource as _envoy_service_discovery_v3_Resource, Resource__Output as _envoy_service_discovery_v3_Resource__Output } from '../../../../envoy/service/discovery/v3/Resource'; +import type { ControlPlane as _envoy_config_core_v3_ControlPlane, ControlPlane__Output as _envoy_config_core_v3_ControlPlane__Output } from '../../../../envoy/config/core/v3/ControlPlane'; + +/** + * [#next-free-field: 8] + */ +export interface DeltaDiscoveryResponse { + /** + * The version of the response data (used for debugging). + */ + 'system_version_info'?: (string); + /** + * The response resources. These are typed resources, whose types must match + * the type_url field. + */ + 'resources'?: (_envoy_service_discovery_v3_Resource)[]; + /** + * Type URL for resources. Identifies the xDS API when muxing over ADS. + * Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. + */ + 'type_url'?: (string); + /** + * The nonce provides a way for DeltaDiscoveryRequests to uniquely + * reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. + */ + 'nonce'?: (string); + /** + * Resources names of resources that have be deleted and to be removed from the xDS Client. + * Removed resources for missing resources can be ignored. + */ + 'removed_resources'?: (string)[]; + /** + * [#not-implemented-hide:] + * The control plane instance that sent the response. + */ + 'control_plane'?: (_envoy_config_core_v3_ControlPlane | null); +} + +/** + * [#next-free-field: 8] + */ +export interface DeltaDiscoveryResponse__Output { + /** + * The version of the response data (used for debugging). + */ + 'system_version_info': (string); + /** + * The response resources. These are typed resources, whose types must match + * the type_url field. + */ + 'resources': (_envoy_service_discovery_v3_Resource__Output)[]; + /** + * Type URL for resources. Identifies the xDS API when muxing over ADS. + * Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. + */ + 'type_url': (string); + /** + * The nonce provides a way for DeltaDiscoveryRequests to uniquely + * reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. + */ + 'nonce': (string); + /** + * Resources names of resources that have be deleted and to be removed from the xDS Client. + * Removed resources for missing resources can be ignored. + */ + 'removed_resources': (string)[]; + /** + * [#not-implemented-hide:] + * The control plane instance that sent the response. + */ + 'control_plane': (_envoy_config_core_v3_ControlPlane__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts new file mode 100644 index 000000000..f392ab8ae --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts @@ -0,0 +1,110 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../../google/rpc/Status'; + +/** + * A DiscoveryRequest requests a set of versioned resources of the same type for + * a given Envoy node on some API. + * [#next-free-field: 7] + */ +export interface DiscoveryRequest { + /** + * The version_info provided in the request messages will be the version_info + * received with the most recent successfully processed response or empty on + * the first request. It is expected that no new request is sent after a + * response is received until the Envoy instance is ready to ACK/NACK the new + * configuration. ACK/NACK takes place by returning the new API config version + * as applied or the previous API config version respectively. Each type_url + * (see below) has an independent version associated with it. + */ + 'version_info'?: (string); + /** + * The node making the request. + */ + 'node'?: (_envoy_config_core_v3_Node | null); + /** + * List of resources to subscribe to, e.g. list of cluster names or a route + * configuration name. If this is empty, all resources for the API are + * returned. LDS/CDS may have empty resource_names, which will cause all + * resources for the Envoy instance to be returned. The LDS and CDS responses + * will then imply a number of resources that need to be fetched via EDS/RDS, + * which will be explicitly enumerated in resource_names. + */ + 'resource_names'?: (string)[]; + /** + * Type of the resource that is being requested, e.g. + * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This is implicit + * in requests made via singleton xDS APIs such as CDS, LDS, etc. but is + * required for ADS. + */ + 'type_url'?: (string); + /** + * nonce corresponding to DiscoveryResponse being ACK/NACKed. See above + * discussion on version_info and the DiscoveryResponse nonce comment. This + * may be empty only if 1) this is a non-persistent-stream xDS such as HTTP, + * or 2) the client has not yet accepted an update in this xDS stream (unlike + * delta, where it is populated only for new explicit ACKs). + */ + 'response_nonce'?: (string); + /** + * This is populated when the previous :ref:`DiscoveryResponse ` + * failed to update configuration. The *message* field in *error_details* provides the Envoy + * internal exception related to the failure. It is only intended for consumption during manual + * debugging, the string provided is not guaranteed to be stable across Envoy versions. + */ + 'error_detail'?: (_google_rpc_Status | null); +} + +/** + * A DiscoveryRequest requests a set of versioned resources of the same type for + * a given Envoy node on some API. + * [#next-free-field: 7] + */ +export interface DiscoveryRequest__Output { + /** + * The version_info provided in the request messages will be the version_info + * received with the most recent successfully processed response or empty on + * the first request. It is expected that no new request is sent after a + * response is received until the Envoy instance is ready to ACK/NACK the new + * configuration. ACK/NACK takes place by returning the new API config version + * as applied or the previous API config version respectively. Each type_url + * (see below) has an independent version associated with it. + */ + 'version_info': (string); + /** + * The node making the request. + */ + 'node': (_envoy_config_core_v3_Node__Output | null); + /** + * List of resources to subscribe to, e.g. list of cluster names or a route + * configuration name. If this is empty, all resources for the API are + * returned. LDS/CDS may have empty resource_names, which will cause all + * resources for the Envoy instance to be returned. The LDS and CDS responses + * will then imply a number of resources that need to be fetched via EDS/RDS, + * which will be explicitly enumerated in resource_names. + */ + 'resource_names': (string)[]; + /** + * Type of the resource that is being requested, e.g. + * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This is implicit + * in requests made via singleton xDS APIs such as CDS, LDS, etc. but is + * required for ADS. + */ + 'type_url': (string); + /** + * nonce corresponding to DiscoveryResponse being ACK/NACKed. See above + * discussion on version_info and the DiscoveryResponse nonce comment. This + * may be empty only if 1) this is a non-persistent-stream xDS such as HTTP, + * or 2) the client has not yet accepted an update in this xDS stream (unlike + * delta, where it is populated only for new explicit ACKs). + */ + 'response_nonce': (string); + /** + * This is populated when the previous :ref:`DiscoveryResponse ` + * failed to update configuration. The *message* field in *error_details* provides the Envoy + * internal exception related to the failure. It is only intended for consumption during manual + * debugging, the string provided is not guaranteed to be stable across Envoy versions. + */ + 'error_detail': (_google_rpc_Status__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts new file mode 100644 index 000000000..874168317 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts @@ -0,0 +1,106 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { ControlPlane as _envoy_config_core_v3_ControlPlane, ControlPlane__Output as _envoy_config_core_v3_ControlPlane__Output } from '../../../../envoy/config/core/v3/ControlPlane'; + +/** + * [#next-free-field: 7] + */ +export interface DiscoveryResponse { + /** + * The version of the response data. + */ + 'version_info'?: (string); + /** + * The response resources. These resources are typed and depend on the API being called. + */ + 'resources'?: (_google_protobuf_Any)[]; + /** + * [#not-implemented-hide:] + * Canary is used to support two Envoy command line flags: + * + * * --terminate-on-canary-transition-failure. When set, Envoy is able to + * terminate if it detects that configuration is stuck at canary. Consider + * this example sequence of updates: + * - Management server applies a canary config successfully. + * - Management server rolls back to a production config. + * - Envoy rejects the new production config. + * Since there is no sensible way to continue receiving configuration + * updates, Envoy will then terminate and apply production config from a + * clean slate. + * * --dry-run-canary. When set, a canary response will never be applied, only + * validated via a dry run. + */ + 'canary'?: (boolean); + /** + * Type URL for resources. Identifies the xDS API when muxing over ADS. + * Must be consistent with the type_url in the 'resources' repeated Any (if non-empty). + */ + 'type_url'?: (string); + /** + * For gRPC based subscriptions, the nonce provides a way to explicitly ack a + * specific DiscoveryResponse in a following DiscoveryRequest. Additional + * messages may have been sent by Envoy to the management server for the + * previous version on the stream prior to this DiscoveryResponse, that were + * unprocessed at response send time. The nonce allows the management server + * to ignore any further DiscoveryRequests for the previous version until a + * DiscoveryRequest bearing the nonce. The nonce is optional and is not + * required for non-stream based xDS implementations. + */ + 'nonce'?: (string); + /** + * The control plane instance that sent the response. + */ + 'control_plane'?: (_envoy_config_core_v3_ControlPlane | null); +} + +/** + * [#next-free-field: 7] + */ +export interface DiscoveryResponse__Output { + /** + * The version of the response data. + */ + 'version_info': (string); + /** + * The response resources. These resources are typed and depend on the API being called. + */ + 'resources': (_google_protobuf_Any__Output)[]; + /** + * [#not-implemented-hide:] + * Canary is used to support two Envoy command line flags: + * + * * --terminate-on-canary-transition-failure. When set, Envoy is able to + * terminate if it detects that configuration is stuck at canary. Consider + * this example sequence of updates: + * - Management server applies a canary config successfully. + * - Management server rolls back to a production config. + * - Envoy rejects the new production config. + * Since there is no sensible way to continue receiving configuration + * updates, Envoy will then terminate and apply production config from a + * clean slate. + * * --dry-run-canary. When set, a canary response will never be applied, only + * validated via a dry run. + */ + 'canary': (boolean); + /** + * Type URL for resources. Identifies the xDS API when muxing over ADS. + * Must be consistent with the type_url in the 'resources' repeated Any (if non-empty). + */ + 'type_url': (string); + /** + * For gRPC based subscriptions, the nonce provides a way to explicitly ack a + * specific DiscoveryResponse in a following DiscoveryRequest. Additional + * messages may have been sent by Envoy to the management server for the + * previous version on the stream prior to this DiscoveryResponse, that were + * unprocessed at response send time. The nonce allows the management server + * to ignore any further DiscoveryRequests for the previous version until a + * DiscoveryRequest bearing the nonce. The nonce is optional and is not + * required for non-stream based xDS implementations. + */ + 'nonce': (string); + /** + * The control plane instance that sent the response. + */ + 'control_plane': (_envoy_config_core_v3_ControlPlane__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts new file mode 100644 index 000000000..0e5897ab4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts @@ -0,0 +1,118 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * Cache control properties for the resource. + * [#not-implemented-hide:] + */ +export interface _envoy_service_discovery_v3_Resource_CacheControl { + /** + * If true, xDS proxies may not cache this resource. + * Note that this does not apply to clients other than xDS proxies, which must cache resources + * for their own use, regardless of the value of this field. + */ + 'do_not_cache'?: (boolean); +} + +/** + * Cache control properties for the resource. + * [#not-implemented-hide:] + */ +export interface _envoy_service_discovery_v3_Resource_CacheControl__Output { + /** + * If true, xDS proxies may not cache this resource. + * Note that this does not apply to clients other than xDS proxies, which must cache resources + * for their own use, regardless of the value of this field. + */ + 'do_not_cache': (boolean); +} + +/** + * [#next-free-field: 8] + */ +export interface Resource { + /** + * The resource level version. It allows xDS to track the state of individual + * resources. + */ + 'version'?: (string); + /** + * The resource being tracked. + */ + 'resource'?: (_google_protobuf_Any | null); + /** + * The resource's name, to distinguish it from others of the same type of resource. + */ + 'name'?: (string); + /** + * The aliases are a list of other names that this resource can go by. + */ + 'aliases'?: (string)[]; + /** + * Time-to-live value for the resource. For each resource, a timer is started. The timer is + * reset each time the resource is received with a new TTL. If the resource is received with + * no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + * configuration for the resource will be removed. + * + * The TTL can be refreshed or changed by sending a response that doesn't change the resource + * version. In this case the resource field does not need to be populated, which allows for + * light-weight "heartbeat" updates to keep a resource with a TTL alive. + * + * The TTL feature is meant to support configurations that should be removed in the event of + * a management server failure. For example, the feature may be used for fault injection + * testing where the fault injection should be terminated in the event that Envoy loses contact + * with the management server. + */ + 'ttl'?: (_google_protobuf_Duration | null); + /** + * Cache control properties for the resource. + * [#not-implemented-hide:] + */ + 'cache_control'?: (_envoy_service_discovery_v3_Resource_CacheControl | null); +} + +/** + * [#next-free-field: 8] + */ +export interface Resource__Output { + /** + * The resource level version. It allows xDS to track the state of individual + * resources. + */ + 'version': (string); + /** + * The resource being tracked. + */ + 'resource': (_google_protobuf_Any__Output | null); + /** + * The resource's name, to distinguish it from others of the same type of resource. + */ + 'name': (string); + /** + * The aliases are a list of other names that this resource can go by. + */ + 'aliases': (string)[]; + /** + * Time-to-live value for the resource. For each resource, a timer is started. The timer is + * reset each time the resource is received with a new TTL. If the resource is received with + * no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + * configuration for the resource will be removed. + * + * The TTL can be refreshed or changed by sending a response that doesn't change the resource + * version. In this case the resource field does not need to be populated, which allows for + * light-weight "heartbeat" updates to keep a resource with a TTL alive. + * + * The TTL feature is meant to support configurations that should be removed in the event of + * a management server failure. For example, the feature may be used for fault injection + * testing where the fault injection should be terminated in the event that Envoy loses contact + * with the management server. + */ + 'ttl': (_google_protobuf_Duration__Output | null); + /** + * Cache control properties for the resource. + * [#not-implemented-hide:] + */ + 'cache_control': (_envoy_service_discovery_v3_Resource_CacheControl__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts new file mode 100644 index 000000000..32aa4f96e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts @@ -0,0 +1,113 @@ +// Original file: deps/envoy-api/envoy/service/load_stats/v3/lrs.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { LoadStatsRequest as _envoy_service_load_stats_v3_LoadStatsRequest, LoadStatsRequest__Output as _envoy_service_load_stats_v3_LoadStatsRequest__Output } from '../../../../envoy/service/load_stats/v3/LoadStatsRequest'; +import type { LoadStatsResponse as _envoy_service_load_stats_v3_LoadStatsResponse, LoadStatsResponse__Output as _envoy_service_load_stats_v3_LoadStatsResponse__Output } from '../../../../envoy/service/load_stats/v3/LoadStatsResponse'; + +export interface LoadReportingServiceClient extends grpc.Client { + /** + * Advanced API to allow for multi-dimensional load balancing by remote + * server. For receiving LB assignments, the steps are: + * 1, The management server is configured with per cluster/zone/load metric + * capacity configuration. The capacity configuration definition is + * outside of the scope of this document. + * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters + * to balance. + * + * Independently, Envoy will initiate a StreamLoadStats bidi stream with a + * management server: + * 1. Once a connection establishes, the management server publishes a + * LoadStatsResponse for all clusters it is interested in learning load + * stats about. + * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts + * based on per-zone weights and/or per-instance weights (if specified) + * based on intra-zone LbPolicy. This information comes from the above + * {Stream,Fetch}Endpoints. + * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. + * 4. Envoy aggregates load reports over the period of time given to it in + * LoadStatsResponse.load_reporting_interval. This includes aggregation + * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as + * well as load metrics from upstream hosts. + * 5. When the timer of load_reporting_interval expires, Envoy sends new + * LoadStatsRequest filled with load reports for each cluster. + * 6. The management server uses the load reports from all reported Envoys + * from around the world, computes global assignment and prepares traffic + * assignment destined for each zone Envoys are located in. Goto 2. + */ + StreamLoadStats(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse__Output>; + StreamLoadStats(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse__Output>; + /** + * Advanced API to allow for multi-dimensional load balancing by remote + * server. For receiving LB assignments, the steps are: + * 1, The management server is configured with per cluster/zone/load metric + * capacity configuration. The capacity configuration definition is + * outside of the scope of this document. + * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters + * to balance. + * + * Independently, Envoy will initiate a StreamLoadStats bidi stream with a + * management server: + * 1. Once a connection establishes, the management server publishes a + * LoadStatsResponse for all clusters it is interested in learning load + * stats about. + * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts + * based on per-zone weights and/or per-instance weights (if specified) + * based on intra-zone LbPolicy. This information comes from the above + * {Stream,Fetch}Endpoints. + * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. + * 4. Envoy aggregates load reports over the period of time given to it in + * LoadStatsResponse.load_reporting_interval. This includes aggregation + * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as + * well as load metrics from upstream hosts. + * 5. When the timer of load_reporting_interval expires, Envoy sends new + * LoadStatsRequest filled with load reports for each cluster. + * 6. The management server uses the load reports from all reported Envoys + * from around the world, computes global assignment and prepares traffic + * assignment destined for each zone Envoys are located in. Goto 2. + */ + streamLoadStats(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse__Output>; + streamLoadStats(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse__Output>; + +} + +export interface LoadReportingServiceHandlers extends grpc.UntypedServiceImplementation { + /** + * Advanced API to allow for multi-dimensional load balancing by remote + * server. For receiving LB assignments, the steps are: + * 1, The management server is configured with per cluster/zone/load metric + * capacity configuration. The capacity configuration definition is + * outside of the scope of this document. + * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters + * to balance. + * + * Independently, Envoy will initiate a StreamLoadStats bidi stream with a + * management server: + * 1. Once a connection establishes, the management server publishes a + * LoadStatsResponse for all clusters it is interested in learning load + * stats about. + * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts + * based on per-zone weights and/or per-instance weights (if specified) + * based on intra-zone LbPolicy. This information comes from the above + * {Stream,Fetch}Endpoints. + * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. + * 4. Envoy aggregates load reports over the period of time given to it in + * LoadStatsResponse.load_reporting_interval. This includes aggregation + * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as + * well as load metrics from upstream hosts. + * 5. When the timer of load_reporting_interval expires, Envoy sends new + * LoadStatsRequest filled with load reports for each cluster. + * 6. The management server uses the load reports from all reported Envoys + * from around the world, computes global assignment and prepares traffic + * assignment destined for each zone Envoys are located in. Goto 2. + */ + StreamLoadStats: grpc.handleBidiStreamingCall<_envoy_service_load_stats_v3_LoadStatsRequest__Output, _envoy_service_load_stats_v3_LoadStatsResponse>; + +} + +export interface LoadReportingServiceDefinition extends grpc.ServiceDefinition { + StreamLoadStats: MethodDefinition<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse, _envoy_service_load_stats_v3_LoadStatsRequest__Output, _envoy_service_load_stats_v3_LoadStatsResponse__Output> +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts new file mode 100644 index 000000000..e430eb270 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts @@ -0,0 +1,32 @@ +// Original file: deps/envoy-api/envoy/service/load_stats/v3/lrs.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { ClusterStats as _envoy_config_endpoint_v3_ClusterStats, ClusterStats__Output as _envoy_config_endpoint_v3_ClusterStats__Output } from '../../../../envoy/config/endpoint/v3/ClusterStats'; + +/** + * A load report Envoy sends to the management server. + */ +export interface LoadStatsRequest { + /** + * Node identifier for Envoy instance. + */ + 'node'?: (_envoy_config_core_v3_Node | null); + /** + * A list of load stats to report. + */ + 'cluster_stats'?: (_envoy_config_endpoint_v3_ClusterStats)[]; +} + +/** + * A load report Envoy sends to the management server. + */ +export interface LoadStatsRequest__Output { + /** + * Node identifier for Envoy instance. + */ + 'node': (_envoy_config_core_v3_Node__Output | null); + /** + * A list of load stats to report. + */ + 'cluster_stats': (_envoy_config_endpoint_v3_ClusterStats__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts new file mode 100644 index 000000000..40f561870 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts @@ -0,0 +1,71 @@ +// Original file: deps/envoy-api/envoy/service/load_stats/v3/lrs.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * The management server sends envoy a LoadStatsResponse with all clusters it + * is interested in learning load stats about. + */ +export interface LoadStatsResponse { + /** + * Clusters to report stats for. + * Not populated if *send_all_clusters* is true. + */ + 'clusters'?: (string)[]; + /** + * The minimum interval of time to collect stats over. This is only a minimum for two reasons: + * + * 1. There may be some delay from when the timer fires until stats sampling occurs. + * 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic + * that is observed in between the corresponding previous *LoadStatsRequest* and this + * *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period + * of inobservability that might otherwise exists between the messages. New clusters are not + * subject to this consideration. + */ + 'load_reporting_interval'?: (_google_protobuf_Duration | null); + /** + * Set to *true* if the management server supports endpoint granularity + * report. + */ + 'report_endpoint_granularity'?: (boolean); + /** + * If true, the client should send all clusters it knows about. + * Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their + * :ref:`client_features` field will honor this field. + */ + 'send_all_clusters'?: (boolean); +} + +/** + * The management server sends envoy a LoadStatsResponse with all clusters it + * is interested in learning load stats about. + */ +export interface LoadStatsResponse__Output { + /** + * Clusters to report stats for. + * Not populated if *send_all_clusters* is true. + */ + 'clusters': (string)[]; + /** + * The minimum interval of time to collect stats over. This is only a minimum for two reasons: + * + * 1. There may be some delay from when the timer fires until stats sampling occurs. + * 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic + * that is observed in between the corresponding previous *LoadStatsRequest* and this + * *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period + * of inobservability that might otherwise exists between the messages. New clusters are not + * subject to this consideration. + */ + 'load_reporting_interval': (_google_protobuf_Duration__Output | null); + /** + * Set to *true* if the management server supports endpoint granularity + * report. + */ + 'report_endpoint_granularity': (boolean); + /** + * If true, the client should send all clusters it knows about. + * Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their + * :ref:`client_features` field will honor this field. + */ + 'send_all_clusters': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts new file mode 100644 index 000000000..ba6b25b4c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts @@ -0,0 +1,159 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { PerXdsConfig as _envoy_service_status_v3_PerXdsConfig, PerXdsConfig__Output as _envoy_service_status_v3_PerXdsConfig__Output } from '../../../../envoy/service/status/v3/PerXdsConfig'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../../google/protobuf/Timestamp'; +import type { ConfigStatus as _envoy_service_status_v3_ConfigStatus } from '../../../../envoy/service/status/v3/ConfigStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../../envoy/admin/v3/ClientResourceStatus'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../../envoy/admin/v3/UpdateFailureState'; + +/** + * GenericXdsConfig is used to specify the config status and the dump + * of any xDS resource identified by their type URL. It is the generalized + * version of the now deprecated ListenersConfigDump, ClustersConfigDump etc + * [#next-free-field: 10] + */ +export interface _envoy_service_status_v3_ClientConfig_GenericXdsConfig { + /** + * Type_url represents the fully qualified name of xDS resource type + * like envoy.v3.Cluster, envoy.v3.ClusterLoadAssignment etc. + */ + 'type_url'?: (string); + /** + * Name of the xDS resource + */ + 'name'?: (string); + /** + * This is the :ref:`version_info ` + * in the last processed xDS discovery response. If there are only + * static bootstrap listeners, this field will be "" + */ + 'version_info'?: (string); + /** + * The xDS resource config. Actual content depends on the type + */ + 'xds_config'?: (_google_protobuf_Any | null); + /** + * Timestamp when the xDS resource was last updated + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Per xDS resource config status. It is generated by management servers. + * It will not be present if the CSDS server is an xDS client. + */ + 'config_status'?: (_envoy_service_status_v3_ConfigStatus | keyof typeof _envoy_service_status_v3_ConfigStatus); + /** + * Per xDS resource status from the view of a xDS client + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + /** + * Set if the last update failed, cleared after the next successful + * update. The *error_state* field contains the rejected version of + * this particular resource along with the reason and timestamp. For + * successfully updated or acknowledged resource, this field should + * be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * Is static resource is true if it is specified in the config supplied + * through the file at the startup. + */ + 'is_static_resource'?: (boolean); +} + +/** + * GenericXdsConfig is used to specify the config status and the dump + * of any xDS resource identified by their type URL. It is the generalized + * version of the now deprecated ListenersConfigDump, ClustersConfigDump etc + * [#next-free-field: 10] + */ +export interface _envoy_service_status_v3_ClientConfig_GenericXdsConfig__Output { + /** + * Type_url represents the fully qualified name of xDS resource type + * like envoy.v3.Cluster, envoy.v3.ClusterLoadAssignment etc. + */ + 'type_url': (string); + /** + * Name of the xDS resource + */ + 'name': (string); + /** + * This is the :ref:`version_info ` + * in the last processed xDS discovery response. If there are only + * static bootstrap listeners, this field will be "" + */ + 'version_info': (string); + /** + * The xDS resource config. Actual content depends on the type + */ + 'xds_config': (_google_protobuf_Any__Output | null); + /** + * Timestamp when the xDS resource was last updated + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Per xDS resource config status. It is generated by management servers. + * It will not be present if the CSDS server is an xDS client. + */ + 'config_status': (keyof typeof _envoy_service_status_v3_ConfigStatus); + /** + * Per xDS resource status from the view of a xDS client + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + /** + * Set if the last update failed, cleared after the next successful + * update. The *error_state* field contains the rejected version of + * this particular resource along with the reason and timestamp. For + * successfully updated or acknowledged resource, this field should + * be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * Is static resource is true if it is specified in the config supplied + * through the file at the startup. + */ + 'is_static_resource': (boolean); +} + +/** + * All xds configs for a particular client. + */ +export interface ClientConfig { + /** + * Node for a particular client. + */ + 'node'?: (_envoy_config_core_v3_Node | null); + /** + * This field is deprecated in favor of generic_xds_configs which is + * much simpler and uniform in structure. + */ + 'xds_config'?: (_envoy_service_status_v3_PerXdsConfig)[]; + /** + * Represents generic xDS config and the exact config structure depends on + * the type URL (like Cluster if it is CDS) + */ + 'generic_xds_configs'?: (_envoy_service_status_v3_ClientConfig_GenericXdsConfig)[]; +} + +/** + * All xds configs for a particular client. + */ +export interface ClientConfig__Output { + /** + * Node for a particular client. + */ + 'node': (_envoy_config_core_v3_Node__Output | null); + /** + * This field is deprecated in favor of generic_xds_configs which is + * much simpler and uniform in structure. + */ + 'xds_config': (_envoy_service_status_v3_PerXdsConfig__Output)[]; + /** + * Represents generic xDS config and the exact config structure depends on + * the type URL (like Cluster if it is CDS) + */ + 'generic_xds_configs': (_envoy_service_status_v3_ClientConfig_GenericXdsConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts new file mode 100644 index 000000000..104445a3f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts @@ -0,0 +1,26 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +/** + * Config status from a client-side view. + */ +export enum ClientConfigStatus { + /** + * Config status is not available/unknown. + */ + CLIENT_UNKNOWN = 0, + /** + * Client requested the config but hasn't received any config from management + * server yet. + */ + CLIENT_REQUESTED = 1, + /** + * Client received the config and replied with ACK. + */ + CLIENT_ACKED = 2, + /** + * Client received the config and replied with NACK. Notably, the attached + * config dump is not the NACKed version, but the most recent accepted one. If + * no config is accepted yet, the attached config dump will be empty. + */ + CLIENT_NACKED = 3, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusDiscoveryService.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusDiscoveryService.ts new file mode 100644 index 000000000..7402fb69b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusDiscoveryService.ts @@ -0,0 +1,45 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { ClientStatusRequest as _envoy_service_status_v3_ClientStatusRequest, ClientStatusRequest__Output as _envoy_service_status_v3_ClientStatusRequest__Output } from '../../../../envoy/service/status/v3/ClientStatusRequest'; +import type { ClientStatusResponse as _envoy_service_status_v3_ClientStatusResponse, ClientStatusResponse__Output as _envoy_service_status_v3_ClientStatusResponse__Output } from '../../../../envoy/service/status/v3/ClientStatusResponse'; + +/** + * CSDS is Client Status Discovery Service. It can be used to get the status of + * an xDS-compliant client from the management server's point of view. It can + * also be used to get the current xDS states directly from the client. + */ +export interface ClientStatusDiscoveryServiceClient extends grpc.Client { + FetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + FetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + FetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + FetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + fetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + fetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + fetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + fetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + + StreamClientStatus(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse__Output>; + StreamClientStatus(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse__Output>; + streamClientStatus(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse__Output>; + streamClientStatus(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse__Output>; + +} + +/** + * CSDS is Client Status Discovery Service. It can be used to get the status of + * an xDS-compliant client from the management server's point of view. It can + * also be used to get the current xDS states directly from the client. + */ +export interface ClientStatusDiscoveryServiceHandlers extends grpc.UntypedServiceImplementation { + FetchClientStatus: grpc.handleUnaryCall<_envoy_service_status_v3_ClientStatusRequest__Output, _envoy_service_status_v3_ClientStatusResponse>; + + StreamClientStatus: grpc.handleBidiStreamingCall<_envoy_service_status_v3_ClientStatusRequest__Output, _envoy_service_status_v3_ClientStatusResponse>; + +} + +export interface ClientStatusDiscoveryServiceDefinition extends grpc.ServiceDefinition { + FetchClientStatus: MethodDefinition<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse, _envoy_service_status_v3_ClientStatusRequest__Output, _envoy_service_status_v3_ClientStatusResponse__Output> + StreamClientStatus: MethodDefinition<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse, _envoy_service_status_v3_ClientStatusRequest__Output, _envoy_service_status_v3_ClientStatusResponse__Output> +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusRequest.ts new file mode 100644 index 000000000..91adddacc --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusRequest.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type { NodeMatcher as _envoy_type_matcher_v3_NodeMatcher, NodeMatcher__Output as _envoy_type_matcher_v3_NodeMatcher__Output } from '../../../../envoy/type/matcher/v3/NodeMatcher'; +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; + +/** + * Request for client status of clients identified by a list of NodeMatchers. + */ +export interface ClientStatusRequest { + /** + * Management server can use these match criteria to identify clients. + * The match follows OR semantics. + */ + 'node_matchers'?: (_envoy_type_matcher_v3_NodeMatcher)[]; + /** + * The node making the csds request. + */ + 'node'?: (_envoy_config_core_v3_Node | null); +} + +/** + * Request for client status of clients identified by a list of NodeMatchers. + */ +export interface ClientStatusRequest__Output { + /** + * Management server can use these match criteria to identify clients. + * The match follows OR semantics. + */ + 'node_matchers': (_envoy_type_matcher_v3_NodeMatcher__Output)[]; + /** + * The node making the csds request. + */ + 'node': (_envoy_config_core_v3_Node__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusResponse.ts new file mode 100644 index 000000000..3611016ec --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusResponse.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type { ClientConfig as _envoy_service_status_v3_ClientConfig, ClientConfig__Output as _envoy_service_status_v3_ClientConfig__Output } from '../../../../envoy/service/status/v3/ClientConfig'; + +export interface ClientStatusResponse { + /** + * Client configs for the clients specified in the ClientStatusRequest. + */ + 'config'?: (_envoy_service_status_v3_ClientConfig)[]; +} + +export interface ClientStatusResponse__Output { + /** + * Client configs for the clients specified in the ClientStatusRequest. + */ + 'config': (_envoy_service_status_v3_ClientConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts new file mode 100644 index 000000000..71db302c3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +/** + * Status of a config from a management server view. + */ +export enum ConfigStatus { + /** + * Status info is not available/unknown. + */ + UNKNOWN = 0, + /** + * Management server has sent the config to client and received ACK. + */ + SYNCED = 1, + /** + * Config is not sent. + */ + NOT_SENT = 2, + /** + * Management server has sent the config to client but hasn’t received + * ACK/NACK. + */ + STALE = 3, + /** + * Management server has sent the config to client but received NACK. The + * attached config dump will be the latest config (the rejected one), since + * it is the persisted version in the management server. + */ + ERROR = 4, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts new file mode 100644 index 000000000..947f1c81a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts @@ -0,0 +1,67 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type { ConfigStatus as _envoy_service_status_v3_ConfigStatus } from '../../../../envoy/service/status/v3/ConfigStatus'; +import type { ListenersConfigDump as _envoy_admin_v3_ListenersConfigDump, ListenersConfigDump__Output as _envoy_admin_v3_ListenersConfigDump__Output } from '../../../../envoy/admin/v3/ListenersConfigDump'; +import type { ClustersConfigDump as _envoy_admin_v3_ClustersConfigDump, ClustersConfigDump__Output as _envoy_admin_v3_ClustersConfigDump__Output } from '../../../../envoy/admin/v3/ClustersConfigDump'; +import type { RoutesConfigDump as _envoy_admin_v3_RoutesConfigDump, RoutesConfigDump__Output as _envoy_admin_v3_RoutesConfigDump__Output } from '../../../../envoy/admin/v3/RoutesConfigDump'; +import type { ScopedRoutesConfigDump as _envoy_admin_v3_ScopedRoutesConfigDump, ScopedRoutesConfigDump__Output as _envoy_admin_v3_ScopedRoutesConfigDump__Output } from '../../../../envoy/admin/v3/ScopedRoutesConfigDump'; +import type { EndpointsConfigDump as _envoy_admin_v3_EndpointsConfigDump, EndpointsConfigDump__Output as _envoy_admin_v3_EndpointsConfigDump__Output } from '../../../../envoy/admin/v3/EndpointsConfigDump'; +import type { ClientConfigStatus as _envoy_service_status_v3_ClientConfigStatus } from '../../../../envoy/service/status/v3/ClientConfigStatus'; + +/** + * Detailed config (per xDS) with status. + * [#next-free-field: 8] + */ +export interface PerXdsConfig { + /** + * Config status generated by management servers. Will not be present if the + * CSDS server is an xDS client. + */ + 'status'?: (_envoy_service_status_v3_ConfigStatus | keyof typeof _envoy_service_status_v3_ConfigStatus); + 'listener_config'?: (_envoy_admin_v3_ListenersConfigDump | null); + 'cluster_config'?: (_envoy_admin_v3_ClustersConfigDump | null); + 'route_config'?: (_envoy_admin_v3_RoutesConfigDump | null); + 'scoped_route_config'?: (_envoy_admin_v3_ScopedRoutesConfigDump | null); + 'endpoint_config'?: (_envoy_admin_v3_EndpointsConfigDump | null); + /** + * Client config status is populated by xDS clients. Will not be present if + * the CSDS server is an xDS server. No matter what the client config status + * is, xDS clients should always dump the most recent accepted xDS config. + * + * .. attention:: + * This field is deprecated. Use :ref:`ClientResourceStatus + * ` for per-resource + * config status instead. + */ + 'client_status'?: (_envoy_service_status_v3_ClientConfigStatus | keyof typeof _envoy_service_status_v3_ClientConfigStatus); + 'per_xds_config'?: "listener_config"|"cluster_config"|"route_config"|"scoped_route_config"|"endpoint_config"; +} + +/** + * Detailed config (per xDS) with status. + * [#next-free-field: 8] + */ +export interface PerXdsConfig__Output { + /** + * Config status generated by management servers. Will not be present if the + * CSDS server is an xDS client. + */ + 'status': (keyof typeof _envoy_service_status_v3_ConfigStatus); + 'listener_config'?: (_envoy_admin_v3_ListenersConfigDump__Output | null); + 'cluster_config'?: (_envoy_admin_v3_ClustersConfigDump__Output | null); + 'route_config'?: (_envoy_admin_v3_RoutesConfigDump__Output | null); + 'scoped_route_config'?: (_envoy_admin_v3_ScopedRoutesConfigDump__Output | null); + 'endpoint_config'?: (_envoy_admin_v3_EndpointsConfigDump__Output | null); + /** + * Client config status is populated by xDS clients. Will not be present if + * the CSDS server is an xDS server. No matter what the client config status + * is, xDS clients should always dump the most recent accepted xDS config. + * + * .. attention:: + * This field is deprecated. Use :ref:`ClientResourceStatus + * ` for per-resource + * config status instead. + */ + 'client_status': (keyof typeof _envoy_service_status_v3_ClientConfigStatus); + 'per_xds_config': "listener_config"|"cluster_config"|"route_config"|"scoped_route_config"|"endpoint_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts b/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts new file mode 100644 index 000000000..4dc10efcb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts @@ -0,0 +1,92 @@ +// Original file: deps/envoy-api/envoy/type/http/v3/path_transformation.proto + + +/** + * Determines if adjacent slashes are merged into one. A common use case is for a request path + * header. Using this option in `:ref: PathNormalizationOptions + * ` + * will allow incoming requests with path `//dir///file` to match against route with `prefix` + * match set to `/dir`. When using for header transformations, note that slash merging is not + * part of `HTTP spec `_ and is provided for convenience. + */ +export interface _envoy_type_http_v3_PathTransformation_Operation_MergeSlashes { +} + +/** + * Determines if adjacent slashes are merged into one. A common use case is for a request path + * header. Using this option in `:ref: PathNormalizationOptions + * ` + * will allow incoming requests with path `//dir///file` to match against route with `prefix` + * match set to `/dir`. When using for header transformations, note that slash merging is not + * part of `HTTP spec `_ and is provided for convenience. + */ +export interface _envoy_type_http_v3_PathTransformation_Operation_MergeSlashes__Output { +} + +/** + * Should text be normalized according to RFC 3986? This typically is used for path headers + * before any processing of requests by HTTP filters or routing. This applies percent-encoded + * normalization and path segment normalization. Fails on characters disallowed in URLs + * (e.g. NULLs). See `Normalization and Comparison + * `_ for details of normalization. Note that + * this options does not perform `case normalization + * `_ + */ +export interface _envoy_type_http_v3_PathTransformation_Operation_NormalizePathRFC3986 { +} + +/** + * Should text be normalized according to RFC 3986? This typically is used for path headers + * before any processing of requests by HTTP filters or routing. This applies percent-encoded + * normalization and path segment normalization. Fails on characters disallowed in URLs + * (e.g. NULLs). See `Normalization and Comparison + * `_ for details of normalization. Note that + * this options does not perform `case normalization + * `_ + */ +export interface _envoy_type_http_v3_PathTransformation_Operation_NormalizePathRFC3986__Output { +} + +/** + * A type of operation to alter text. + */ +export interface _envoy_type_http_v3_PathTransformation_Operation { + /** + * Enable path normalization per RFC 3986. + */ + 'normalize_path_rfc_3986'?: (_envoy_type_http_v3_PathTransformation_Operation_NormalizePathRFC3986 | null); + /** + * Enable merging adjacent slashes. + */ + 'merge_slashes'?: (_envoy_type_http_v3_PathTransformation_Operation_MergeSlashes | null); + 'operation_specifier'?: "normalize_path_rfc_3986"|"merge_slashes"; +} + +/** + * A type of operation to alter text. + */ +export interface _envoy_type_http_v3_PathTransformation_Operation__Output { + /** + * Enable path normalization per RFC 3986. + */ + 'normalize_path_rfc_3986'?: (_envoy_type_http_v3_PathTransformation_Operation_NormalizePathRFC3986__Output | null); + /** + * Enable merging adjacent slashes. + */ + 'merge_slashes'?: (_envoy_type_http_v3_PathTransformation_Operation_MergeSlashes__Output | null); + 'operation_specifier': "normalize_path_rfc_3986"|"merge_slashes"; +} + +export interface PathTransformation { + /** + * A list of operations to apply. Transformations will be performed in the order that they appear. + */ + 'operations'?: (_envoy_type_http_v3_PathTransformation_Operation)[]; +} + +export interface PathTransformation__Output { + /** + * A list of operations to apply. Transformations will be performed in the order that they appear. + */ + 'operations': (_envoy_type_http_v3_PathTransformation_Operation__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts new file mode 100644 index 000000000..0bf3bca79 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/number.proto + +import type { DoubleRange as _envoy_type_v3_DoubleRange, DoubleRange__Output as _envoy_type_v3_DoubleRange__Output } from '../../../../envoy/type/v3/DoubleRange'; + +/** + * Specifies the way to match a double value. + */ +export interface DoubleMatcher { + /** + * If specified, the input double value must be in the range specified here. + * Note: The range is using half-open interval semantics [start, end). + */ + 'range'?: (_envoy_type_v3_DoubleRange | null); + /** + * If specified, the input double value must be equal to the value specified here. + */ + 'exact'?: (number | string); + 'match_pattern'?: "range"|"exact"; +} + +/** + * Specifies the way to match a double value. + */ +export interface DoubleMatcher__Output { + /** + * If specified, the input double value must be in the range specified here. + * Note: The range is using half-open interval semantics [start, end). + */ + 'range'?: (_envoy_type_v3_DoubleRange__Output | null); + /** + * If specified, the input double value must be equal to the value specified here. + */ + 'exact'?: (number); + 'match_pattern': "range"|"exact"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts new file mode 100644 index 000000000..10bf5567c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/value.proto + +import type { ValueMatcher as _envoy_type_matcher_v3_ValueMatcher, ValueMatcher__Output as _envoy_type_matcher_v3_ValueMatcher__Output } from '../../../../envoy/type/matcher/v3/ValueMatcher'; + +/** + * Specifies the way to match a list value. + */ +export interface ListMatcher { + /** + * If specified, at least one of the values in the list must match the value specified. + */ + 'one_of'?: (_envoy_type_matcher_v3_ValueMatcher | null); + 'match_pattern'?: "one_of"; +} + +/** + * Specifies the way to match a list value. + */ +export interface ListMatcher__Output { + /** + * If specified, at least one of the values in the list must match the value specified. + */ + 'one_of'?: (_envoy_type_matcher_v3_ValueMatcher__Output | null); + 'match_pattern': "one_of"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListStringMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListStringMatcher.ts new file mode 100644 index 000000000..4d5062382 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListStringMatcher.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/string.proto + +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; + +/** + * Specifies a list of ways to match a string. + */ +export interface ListStringMatcher { + 'patterns'?: (_envoy_type_matcher_v3_StringMatcher)[]; +} + +/** + * Specifies a list of ways to match a string. + */ +export interface ListStringMatcher__Output { + 'patterns': (_envoy_type_matcher_v3_StringMatcher__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts new file mode 100644 index 000000000..78d4f03da --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts @@ -0,0 +1,73 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/metadata.proto + +import type { ValueMatcher as _envoy_type_matcher_v3_ValueMatcher, ValueMatcher__Output as _envoy_type_matcher_v3_ValueMatcher__Output } from '../../../../envoy/type/matcher/v3/ValueMatcher'; + +/** + * Specifies the segment in a path to retrieve value from Metadata. + * Note: Currently it's not supported to retrieve a value from a list in Metadata. This means that + * if the segment key refers to a list, it has to be the last segment in a path. + */ +export interface _envoy_type_matcher_v3_MetadataMatcher_PathSegment { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment'?: "key"; +} + +/** + * Specifies the segment in a path to retrieve value from Metadata. + * Note: Currently it's not supported to retrieve a value from a list in Metadata. This means that + * if the segment key refers to a list, it has to be the last segment in a path. + */ +export interface _envoy_type_matcher_v3_MetadataMatcher_PathSegment__Output { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment': "key"; +} + +/** + * [#next-major-version: MetadataMatcher should use StructMatcher] + */ +export interface MetadataMatcher { + /** + * The filter name to retrieve the Struct from the Metadata. + */ + 'filter'?: (string); + /** + * The path to retrieve the Value from the Struct. + */ + 'path'?: (_envoy_type_matcher_v3_MetadataMatcher_PathSegment)[]; + /** + * The MetadataMatcher is matched if the value retrieved by path is matched to this value. + */ + 'value'?: (_envoy_type_matcher_v3_ValueMatcher | null); + /** + * If true, the match result will be inverted. + */ + 'invert'?: (boolean); +} + +/** + * [#next-major-version: MetadataMatcher should use StructMatcher] + */ +export interface MetadataMatcher__Output { + /** + * The filter name to retrieve the Struct from the Metadata. + */ + 'filter': (string); + /** + * The path to retrieve the Value from the Struct. + */ + 'path': (_envoy_type_matcher_v3_MetadataMatcher_PathSegment__Output)[]; + /** + * The MetadataMatcher is matched if the value retrieved by path is matched to this value. + */ + 'value': (_envoy_type_matcher_v3_ValueMatcher__Output | null); + /** + * If true, the match result will be inverted. + */ + 'invert': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/NodeMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/NodeMatcher.ts new file mode 100644 index 000000000..7e31b9753 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/NodeMatcher.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/node.proto + +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; +import type { StructMatcher as _envoy_type_matcher_v3_StructMatcher, StructMatcher__Output as _envoy_type_matcher_v3_StructMatcher__Output } from '../../../../envoy/type/matcher/v3/StructMatcher'; + +/** + * Specifies the way to match a Node. + * The match follows AND semantics. + */ +export interface NodeMatcher { + /** + * Specifies match criteria on the node id. + */ + 'node_id'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * Specifies match criteria on the node metadata. + */ + 'node_metadatas'?: (_envoy_type_matcher_v3_StructMatcher)[]; +} + +/** + * Specifies the way to match a Node. + * The match follows AND semantics. + */ +export interface NodeMatcher__Output { + /** + * Specifies match criteria on the node id. + */ + 'node_id': (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * Specifies match criteria on the node metadata. + */ + 'node_metadatas': (_envoy_type_matcher_v3_StructMatcher__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts new file mode 100644 index 000000000..3796765a7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts @@ -0,0 +1,65 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/regex.proto + +import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; + +/** + * Describes how to match a string and then produce a new string using a regular + * expression and a substitution string. + */ +export interface RegexMatchAndSubstitute { + /** + * The regular expression used to find portions of a string (hereafter called + * the "subject string") that should be replaced. When a new string is + * produced during the substitution operation, the new string is initially + * the same as the subject string, but then all matches in the subject string + * are replaced by the substitution string. If replacing all matches isn't + * desired, regular expression anchors can be used to ensure a single match, + * so as to replace just one occurrence of a pattern. Capture groups can be + * used in the pattern to extract portions of the subject string, and then + * referenced in the substitution string. + */ + 'pattern'?: (_envoy_type_matcher_v3_RegexMatcher | null); + /** + * The string that should be substituted into matching portions of the + * subject string during a substitution operation to produce a new string. + * Capture groups in the pattern can be referenced in the substitution + * string. Note, however, that the syntax for referring to capture groups is + * defined by the chosen regular expression engine. Google's `RE2 + * `_ regular expression engine uses a + * backslash followed by the capture group number to denote a numbered + * capture group. E.g., ``\1`` refers to capture group 1, and ``\2`` refers + * to capture group 2. + */ + 'substitution'?: (string); +} + +/** + * Describes how to match a string and then produce a new string using a regular + * expression and a substitution string. + */ +export interface RegexMatchAndSubstitute__Output { + /** + * The regular expression used to find portions of a string (hereafter called + * the "subject string") that should be replaced. When a new string is + * produced during the substitution operation, the new string is initially + * the same as the subject string, but then all matches in the subject string + * are replaced by the substitution string. If replacing all matches isn't + * desired, regular expression anchors can be used to ensure a single match, + * so as to replace just one occurrence of a pattern. Capture groups can be + * used in the pattern to extract portions of the subject string, and then + * referenced in the substitution string. + */ + 'pattern': (_envoy_type_matcher_v3_RegexMatcher__Output | null); + /** + * The string that should be substituted into matching portions of the + * subject string during a substitution operation to produce a new string. + * Capture groups in the pattern can be referenced in the substitution + * string. Note, however, that the syntax for referring to capture groups is + * defined by the chosen regular expression engine. Google's `RE2 + * `_ regular expression engine uses a + * backslash followed by the capture group number to denote a numbered + * capture group. E.g., ``\1`` refers to capture group 1, and ``\2`` refers + * to capture group 2. + */ + 'substitution': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts new file mode 100644 index 000000000..1addb1730 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts @@ -0,0 +1,89 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/regex.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +/** + * Google's `RE2 `_ regex engine. The regex string must adhere to + * the documented `syntax `_. The engine is designed + * to complete execution in linear time as well as limit the amount of memory used. + * + * Envoy supports program size checking via runtime. The runtime keys `re2.max_program_size.error_level` + * and `re2.max_program_size.warn_level` can be set to integers as the maximum program size or + * complexity that a compiled regex can have before an exception is thrown or a warning is + * logged, respectively. `re2.max_program_size.error_level` defaults to 100, and + * `re2.max_program_size.warn_level` has no default if unset (will not check/log a warning). + * + * Envoy emits two stats for tracking the program size of regexes: the histogram `re2.program_size`, + * which records the program size, and the counter `re2.exceeded_warn_level`, which is incremented + * each time the program size exceeds the warn level threshold. + */ +export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2 { + /** + * This field controls the RE2 "program size" which is a rough estimate of how complex a + * compiled regex is to evaluate. A regex that has a program size greater than the configured + * value will fail to compile. In this case, the configured max program size can be increased + * or the regex can be simplified. If not specified, the default is 100. + * + * This field is deprecated; regexp validation should be performed on the management server + * instead of being done by each individual client. + */ + 'max_program_size'?: (_google_protobuf_UInt32Value | null); +} + +/** + * Google's `RE2 `_ regex engine. The regex string must adhere to + * the documented `syntax `_. The engine is designed + * to complete execution in linear time as well as limit the amount of memory used. + * + * Envoy supports program size checking via runtime. The runtime keys `re2.max_program_size.error_level` + * and `re2.max_program_size.warn_level` can be set to integers as the maximum program size or + * complexity that a compiled regex can have before an exception is thrown or a warning is + * logged, respectively. `re2.max_program_size.error_level` defaults to 100, and + * `re2.max_program_size.warn_level` has no default if unset (will not check/log a warning). + * + * Envoy emits two stats for tracking the program size of regexes: the histogram `re2.program_size`, + * which records the program size, and the counter `re2.exceeded_warn_level`, which is incremented + * each time the program size exceeds the warn level threshold. + */ +export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output { + /** + * This field controls the RE2 "program size" which is a rough estimate of how complex a + * compiled regex is to evaluate. A regex that has a program size greater than the configured + * value will fail to compile. In this case, the configured max program size can be increased + * or the regex can be simplified. If not specified, the default is 100. + * + * This field is deprecated; regexp validation should be performed on the management server + * instead of being done by each individual client. + */ + 'max_program_size': (_google_protobuf_UInt32Value__Output | null); +} + +/** + * A regex matcher designed for safety when used with untrusted input. + */ +export interface RegexMatcher { + /** + * Google's RE2 regex engine. + */ + 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2 | null); + /** + * The regex match string. The string must be supported by the configured engine. + */ + 'regex'?: (string); + 'engine_type'?: "google_re2"; +} + +/** + * A regex matcher designed for safety when used with untrusted input. + */ +export interface RegexMatcher__Output { + /** + * Google's RE2 regex engine. + */ + 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output | null); + /** + * The regex match string. The string must be supported by the configured engine. + */ + 'regex': (string); + 'engine_type': "google_re2"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts new file mode 100644 index 000000000..7440746f1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts @@ -0,0 +1,109 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/string.proto + +import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; + +/** + * Specifies the way to match a string. + * [#next-free-field: 8] + */ +export interface StringMatcher { + /** + * The input string must match exactly the string specified here. + * + * Examples: + * + * * *abc* only matches the value *abc*. + */ + 'exact'?: (string); + /** + * The input string must have the prefix specified here. + * Note: empty prefix is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *abc.xyz* + */ + 'prefix'?: (string); + /** + * The input string must have the suffix specified here. + * Note: empty prefix is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc* + */ + 'suffix'?: (string); + /** + * The input string must match the regular expression specified here. + */ + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher | null); + /** + * If true, indicates the exact/prefix/suffix/contains matching should be case insensitive. This + * has no effect for the safe_regex match. + * For example, the matcher *data* will match both input string *Data* and *data* if set to true. + */ + 'ignore_case'?: (boolean); + /** + * The input string must have the substring specified here. + * Note: empty contains match is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc.def* + */ + 'contains'?: (string); + 'match_pattern'?: "exact"|"prefix"|"suffix"|"safe_regex"|"contains"; +} + +/** + * Specifies the way to match a string. + * [#next-free-field: 8] + */ +export interface StringMatcher__Output { + /** + * The input string must match exactly the string specified here. + * + * Examples: + * + * * *abc* only matches the value *abc*. + */ + 'exact'?: (string); + /** + * The input string must have the prefix specified here. + * Note: empty prefix is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *abc.xyz* + */ + 'prefix'?: (string); + /** + * The input string must have the suffix specified here. + * Note: empty prefix is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc* + */ + 'suffix'?: (string); + /** + * The input string must match the regular expression specified here. + */ + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); + /** + * If true, indicates the exact/prefix/suffix/contains matching should be case insensitive. This + * has no effect for the safe_regex match. + * For example, the matcher *data* will match both input string *Data* and *data* if set to true. + */ + 'ignore_case': (boolean); + /** + * The input string must have the substring specified here. + * Note: empty contains match is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc.def* + */ + 'contains'?: (string); + 'match_pattern': "exact"|"prefix"|"suffix"|"safe_regex"|"contains"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts new file mode 100644 index 000000000..141806489 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts @@ -0,0 +1,153 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/struct.proto + +import type { ValueMatcher as _envoy_type_matcher_v3_ValueMatcher, ValueMatcher__Output as _envoy_type_matcher_v3_ValueMatcher__Output } from '../../../../envoy/type/matcher/v3/ValueMatcher'; + +/** + * Specifies the segment in a path to retrieve value from Struct. + */ +export interface _envoy_type_matcher_v3_StructMatcher_PathSegment { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment'?: "key"; +} + +/** + * Specifies the segment in a path to retrieve value from Struct. + */ +export interface _envoy_type_matcher_v3_StructMatcher_PathSegment__Output { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment': "key"; +} + +/** + * StructMatcher provides a general interface to check if a given value is matched in + * google.protobuf.Struct. It uses `path` to retrieve the value + * from the struct and then check if it's matched to the specified value. + * + * For example, for the following Struct: + * + * .. code-block:: yaml + * + * fields: + * a: + * struct_value: + * fields: + * b: + * struct_value: + * fields: + * c: + * string_value: pro + * t: + * list_value: + * values: + * - string_value: m + * - string_value: n + * + * The following MetadataMatcher is matched as the path [a, b, c] will retrieve a string value "pro" + * from the Metadata which is matched to the specified prefix match. + * + * .. code-block:: yaml + * + * path: + * - key: a + * - key: b + * - key: c + * value: + * string_match: + * prefix: pr + * + * The following StructMatcher is matched as the code will match one of the string values in the + * list at the path [a, t]. + * + * .. code-block:: yaml + * + * path: + * - key: a + * - key: t + * value: + * list_match: + * one_of: + * string_match: + * exact: m + * + * An example use of StructMatcher is to match metadata in envoy.v*.core.Node. + */ +export interface StructMatcher { + /** + * The path to retrieve the Value from the Struct. + */ + 'path'?: (_envoy_type_matcher_v3_StructMatcher_PathSegment)[]; + /** + * The StructMatcher is matched if the value retrieved by path is matched to this value. + */ + 'value'?: (_envoy_type_matcher_v3_ValueMatcher | null); +} + +/** + * StructMatcher provides a general interface to check if a given value is matched in + * google.protobuf.Struct. It uses `path` to retrieve the value + * from the struct and then check if it's matched to the specified value. + * + * For example, for the following Struct: + * + * .. code-block:: yaml + * + * fields: + * a: + * struct_value: + * fields: + * b: + * struct_value: + * fields: + * c: + * string_value: pro + * t: + * list_value: + * values: + * - string_value: m + * - string_value: n + * + * The following MetadataMatcher is matched as the path [a, b, c] will retrieve a string value "pro" + * from the Metadata which is matched to the specified prefix match. + * + * .. code-block:: yaml + * + * path: + * - key: a + * - key: b + * - key: c + * value: + * string_match: + * prefix: pr + * + * The following StructMatcher is matched as the code will match one of the string values in the + * list at the path [a, t]. + * + * .. code-block:: yaml + * + * path: + * - key: a + * - key: t + * value: + * list_match: + * one_of: + * string_match: + * exact: m + * + * An example use of StructMatcher is to match metadata in envoy.v*.core.Node. + */ +export interface StructMatcher__Output { + /** + * The path to retrieve the Value from the Struct. + */ + 'path': (_envoy_type_matcher_v3_StructMatcher_PathSegment__Output)[]; + /** + * The StructMatcher is matched if the value retrieved by path is matched to this value. + */ + 'value': (_envoy_type_matcher_v3_ValueMatcher__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts new file mode 100644 index 000000000..d01060446 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts @@ -0,0 +1,101 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/value.proto + +import type { DoubleMatcher as _envoy_type_matcher_v3_DoubleMatcher, DoubleMatcher__Output as _envoy_type_matcher_v3_DoubleMatcher__Output } from '../../../../envoy/type/matcher/v3/DoubleMatcher'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; +import type { ListMatcher as _envoy_type_matcher_v3_ListMatcher, ListMatcher__Output as _envoy_type_matcher_v3_ListMatcher__Output } from '../../../../envoy/type/matcher/v3/ListMatcher'; + +/** + * NullMatch is an empty message to specify a null value. + */ +export interface _envoy_type_matcher_v3_ValueMatcher_NullMatch { +} + +/** + * NullMatch is an empty message to specify a null value. + */ +export interface _envoy_type_matcher_v3_ValueMatcher_NullMatch__Output { +} + +/** + * Specifies the way to match a ProtobufWkt::Value. Primitive values and ListValue are supported. + * StructValue is not supported and is always not matched. + * [#next-free-field: 7] + */ +export interface ValueMatcher { + /** + * If specified, a match occurs if and only if the target value is a NullValue. + */ + 'null_match'?: (_envoy_type_matcher_v3_ValueMatcher_NullMatch | null); + /** + * If specified, a match occurs if and only if the target value is a double value and is + * matched to this field. + */ + 'double_match'?: (_envoy_type_matcher_v3_DoubleMatcher | null); + /** + * If specified, a match occurs if and only if the target value is a string value and is + * matched to this field. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * If specified, a match occurs if and only if the target value is a bool value and is equal + * to this field. + */ + 'bool_match'?: (boolean); + /** + * If specified, value match will be performed based on whether the path is referring to a + * valid primitive value in the metadata. If the path is referring to a non-primitive value, + * the result is always not matched. + */ + 'present_match'?: (boolean); + /** + * If specified, a match occurs if and only if the target value is a list value and + * is matched to this field. + */ + 'list_match'?: (_envoy_type_matcher_v3_ListMatcher | null); + /** + * Specifies how to match a value. + */ + 'match_pattern'?: "null_match"|"double_match"|"string_match"|"bool_match"|"present_match"|"list_match"; +} + +/** + * Specifies the way to match a ProtobufWkt::Value. Primitive values and ListValue are supported. + * StructValue is not supported and is always not matched. + * [#next-free-field: 7] + */ +export interface ValueMatcher__Output { + /** + * If specified, a match occurs if and only if the target value is a NullValue. + */ + 'null_match'?: (_envoy_type_matcher_v3_ValueMatcher_NullMatch__Output | null); + /** + * If specified, a match occurs if and only if the target value is a double value and is + * matched to this field. + */ + 'double_match'?: (_envoy_type_matcher_v3_DoubleMatcher__Output | null); + /** + * If specified, a match occurs if and only if the target value is a string value and is + * matched to this field. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * If specified, a match occurs if and only if the target value is a bool value and is equal + * to this field. + */ + 'bool_match'?: (boolean); + /** + * If specified, value match will be performed based on whether the path is referring to a + * valid primitive value in the metadata. If the path is referring to a non-primitive value, + * the result is always not matched. + */ + 'present_match'?: (boolean); + /** + * If specified, a match occurs if and only if the target value is a list value and + * is matched to this field. + */ + 'list_match'?: (_envoy_type_matcher_v3_ListMatcher__Output | null); + /** + * Specifies how to match a value. + */ + 'match_pattern': "null_match"|"double_match"|"string_match"|"bool_match"|"present_match"|"list_match"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts new file mode 100644 index 000000000..50b6690d3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts @@ -0,0 +1,108 @@ +// Original file: deps/envoy-api/envoy/type/metadata/v3/metadata.proto + + +/** + * Specifies the segment in a path to retrieve value from Metadata. + * Currently it is only supported to specify the key, i.e. field name, as one segment of a path. + */ +export interface _envoy_type_metadata_v3_MetadataKey_PathSegment { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment'?: "key"; +} + +/** + * Specifies the segment in a path to retrieve value from Metadata. + * Currently it is only supported to specify the key, i.e. field name, as one segment of a path. + */ +export interface _envoy_type_metadata_v3_MetadataKey_PathSegment__Output { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment': "key"; +} + +/** + * MetadataKey provides a general interface using `key` and `path` to retrieve value from + * :ref:`Metadata `. + * + * For example, for the following Metadata: + * + * .. code-block:: yaml + * + * filter_metadata: + * envoy.xxx: + * prop: + * foo: bar + * xyz: + * hello: envoy + * + * The following MetadataKey will retrieve a string value "bar" from the Metadata. + * + * .. code-block:: yaml + * + * key: envoy.xxx + * path: + * - key: prop + * - key: foo + */ +export interface MetadataKey { + /** + * The key name of Metadata to retrieve the Struct from the metadata. + * Typically, it represents a builtin subsystem or custom extension. + */ + 'key'?: (string); + /** + * The path to retrieve the Value from the Struct. It can be a prefix or a full path, + * e.g. ``[prop, xyz]`` for a struct or ``[prop, foo]`` for a string in the example, + * which depends on the particular scenario. + * + * Note: Due to that only the key type segment is supported, the path can not specify a list + * unless the list is the last segment. + */ + 'path'?: (_envoy_type_metadata_v3_MetadataKey_PathSegment)[]; +} + +/** + * MetadataKey provides a general interface using `key` and `path` to retrieve value from + * :ref:`Metadata `. + * + * For example, for the following Metadata: + * + * .. code-block:: yaml + * + * filter_metadata: + * envoy.xxx: + * prop: + * foo: bar + * xyz: + * hello: envoy + * + * The following MetadataKey will retrieve a string value "bar" from the Metadata. + * + * .. code-block:: yaml + * + * key: envoy.xxx + * path: + * - key: prop + * - key: foo + */ +export interface MetadataKey__Output { + /** + * The key name of Metadata to retrieve the Struct from the metadata. + * Typically, it represents a builtin subsystem or custom extension. + */ + 'key': (string); + /** + * The path to retrieve the Value from the Struct. It can be a prefix or a full path, + * e.g. ``[prop, xyz]`` for a struct or ``[prop, foo]`` for a string in the example, + * which depends on the particular scenario. + * + * Note: Due to that only the key type segment is supported, the path can not specify a list + * unless the list is the last segment. + */ + 'path': (_envoy_type_metadata_v3_MetadataKey_PathSegment__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts new file mode 100644 index 000000000..3ca368ccb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts @@ -0,0 +1,98 @@ +// Original file: deps/envoy-api/envoy/type/metadata/v3/metadata.proto + + +/** + * Represents metadata from :ref:`the upstream cluster`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Cluster { +} + +/** + * Represents metadata from :ref:`the upstream cluster`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Cluster__Output { +} + +/** + * Represents metadata from :ref:`the upstream + * host`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Host { +} + +/** + * Represents metadata from :ref:`the upstream + * host`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Host__Output { +} + +/** + * Represents dynamic metadata associated with the request. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Request { +} + +/** + * Represents dynamic metadata associated with the request. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Request__Output { +} + +/** + * Represents metadata from :ref:`the route`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Route { +} + +/** + * Represents metadata from :ref:`the route`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Route__Output { +} + +/** + * Describes what kind of metadata. + */ +export interface MetadataKind { + /** + * Request kind of metadata. + */ + 'request'?: (_envoy_type_metadata_v3_MetadataKind_Request | null); + /** + * Route kind of metadata. + */ + 'route'?: (_envoy_type_metadata_v3_MetadataKind_Route | null); + /** + * Cluster kind of metadata. + */ + 'cluster'?: (_envoy_type_metadata_v3_MetadataKind_Cluster | null); + /** + * Host kind of metadata. + */ + 'host'?: (_envoy_type_metadata_v3_MetadataKind_Host | null); + 'kind'?: "request"|"route"|"cluster"|"host"; +} + +/** + * Describes what kind of metadata. + */ +export interface MetadataKind__Output { + /** + * Request kind of metadata. + */ + 'request'?: (_envoy_type_metadata_v3_MetadataKind_Request__Output | null); + /** + * Route kind of metadata. + */ + 'route'?: (_envoy_type_metadata_v3_MetadataKind_Route__Output | null); + /** + * Cluster kind of metadata. + */ + 'cluster'?: (_envoy_type_metadata_v3_MetadataKind_Cluster__Output | null); + /** + * Host kind of metadata. + */ + 'host'?: (_envoy_type_metadata_v3_MetadataKind_Host__Output | null); + 'kind': "request"|"route"|"cluster"|"host"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts b/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts new file mode 100644 index 000000000..34ac26f8c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts @@ -0,0 +1,198 @@ +// Original file: deps/envoy-api/envoy/type/tracing/v3/custom_tag.proto + +import type { MetadataKind as _envoy_type_metadata_v3_MetadataKind, MetadataKind__Output as _envoy_type_metadata_v3_MetadataKind__Output } from '../../../../envoy/type/metadata/v3/MetadataKind'; +import type { MetadataKey as _envoy_type_metadata_v3_MetadataKey, MetadataKey__Output as _envoy_type_metadata_v3_MetadataKey__Output } from '../../../../envoy/type/metadata/v3/MetadataKey'; + +/** + * Environment type custom tag with environment name and default value. + */ +export interface _envoy_type_tracing_v3_CustomTag_Environment { + /** + * Environment variable name to obtain the value to populate the tag value. + */ + 'name'?: (string); + /** + * When the environment variable is not found, + * the tag value will be populated with this default value if specified, + * otherwise no tag will be populated. + */ + 'default_value'?: (string); +} + +/** + * Environment type custom tag with environment name and default value. + */ +export interface _envoy_type_tracing_v3_CustomTag_Environment__Output { + /** + * Environment variable name to obtain the value to populate the tag value. + */ + 'name': (string); + /** + * When the environment variable is not found, + * the tag value will be populated with this default value if specified, + * otherwise no tag will be populated. + */ + 'default_value': (string); +} + +/** + * Header type custom tag with header name and default value. + */ +export interface _envoy_type_tracing_v3_CustomTag_Header { + /** + * Header name to obtain the value to populate the tag value. + */ + 'name'?: (string); + /** + * When the header does not exist, + * the tag value will be populated with this default value if specified, + * otherwise no tag will be populated. + */ + 'default_value'?: (string); +} + +/** + * Header type custom tag with header name and default value. + */ +export interface _envoy_type_tracing_v3_CustomTag_Header__Output { + /** + * Header name to obtain the value to populate the tag value. + */ + 'name': (string); + /** + * When the header does not exist, + * the tag value will be populated with this default value if specified, + * otherwise no tag will be populated. + */ + 'default_value': (string); +} + +/** + * Literal type custom tag with static value for the tag value. + */ +export interface _envoy_type_tracing_v3_CustomTag_Literal { + /** + * Static literal value to populate the tag value. + */ + 'value'?: (string); +} + +/** + * Literal type custom tag with static value for the tag value. + */ +export interface _envoy_type_tracing_v3_CustomTag_Literal__Output { + /** + * Static literal value to populate the tag value. + */ + 'value': (string); +} + +/** + * Metadata type custom tag using + * :ref:`MetadataKey ` to retrieve the protobuf value + * from :ref:`Metadata `, and populate the tag value with + * `the canonical JSON `_ + * representation of it. + */ +export interface _envoy_type_tracing_v3_CustomTag_Metadata { + /** + * Specify what kind of metadata to obtain tag value from. + */ + 'kind'?: (_envoy_type_metadata_v3_MetadataKind | null); + /** + * Metadata key to define the path to retrieve the tag value. + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); + /** + * When no valid metadata is found, + * the tag value would be populated with this default value if specified, + * otherwise no tag would be populated. + */ + 'default_value'?: (string); +} + +/** + * Metadata type custom tag using + * :ref:`MetadataKey ` to retrieve the protobuf value + * from :ref:`Metadata `, and populate the tag value with + * `the canonical JSON `_ + * representation of it. + */ +export interface _envoy_type_tracing_v3_CustomTag_Metadata__Output { + /** + * Specify what kind of metadata to obtain tag value from. + */ + 'kind': (_envoy_type_metadata_v3_MetadataKind__Output | null); + /** + * Metadata key to define the path to retrieve the tag value. + */ + 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); + /** + * When no valid metadata is found, + * the tag value would be populated with this default value if specified, + * otherwise no tag would be populated. + */ + 'default_value': (string); +} + +/** + * Describes custom tags for the active span. + * [#next-free-field: 6] + */ +export interface CustomTag { + /** + * Used to populate the tag name. + */ + 'tag'?: (string); + /** + * A literal custom tag. + */ + 'literal'?: (_envoy_type_tracing_v3_CustomTag_Literal | null); + /** + * An environment custom tag. + */ + 'environment'?: (_envoy_type_tracing_v3_CustomTag_Environment | null); + /** + * A request header custom tag. + */ + 'request_header'?: (_envoy_type_tracing_v3_CustomTag_Header | null); + /** + * A custom tag to obtain tag value from the metadata. + */ + 'metadata'?: (_envoy_type_tracing_v3_CustomTag_Metadata | null); + /** + * Used to specify what kind of custom tag. + */ + 'type'?: "literal"|"environment"|"request_header"|"metadata"; +} + +/** + * Describes custom tags for the active span. + * [#next-free-field: 6] + */ +export interface CustomTag__Output { + /** + * Used to populate the tag name. + */ + 'tag': (string); + /** + * A literal custom tag. + */ + 'literal'?: (_envoy_type_tracing_v3_CustomTag_Literal__Output | null); + /** + * An environment custom tag. + */ + 'environment'?: (_envoy_type_tracing_v3_CustomTag_Environment__Output | null); + /** + * A request header custom tag. + */ + 'request_header'?: (_envoy_type_tracing_v3_CustomTag_Header__Output | null); + /** + * A custom tag to obtain tag value from the metadata. + */ + 'metadata'?: (_envoy_type_tracing_v3_CustomTag_Metadata__Output | null); + /** + * Used to specify what kind of custom tag. + */ + 'type': "literal"|"environment"|"request_header"|"metadata"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts new file mode 100644 index 000000000..308f14446 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts @@ -0,0 +1,12 @@ +// Original file: deps/envoy-api/envoy/type/v3/http.proto + +export enum CodecClientType { + HTTP1 = 0, + HTTP2 = 1, + /** + * [#not-implemented-hide:] QUIC implementation is not production ready yet. Use this enum with + * caution to prevent accidental execution of QUIC code. I.e. `!= HTTP2` is no longer sufficient + * to distinguish HTTP1 and HTTP2 traffic. + */ + HTTP3 = 2, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/DoubleRange.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/DoubleRange.ts new file mode 100644 index 000000000..5d13bebdb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/DoubleRange.ts @@ -0,0 +1,32 @@ +// Original file: deps/envoy-api/envoy/type/v3/range.proto + + +/** + * Specifies the double start and end of the range using half-open interval semantics [start, + * end). + */ +export interface DoubleRange { + /** + * start of the range (inclusive) + */ + 'start'?: (number | string); + /** + * end of the range (exclusive) + */ + 'end'?: (number | string); +} + +/** + * Specifies the double start and end of the range using half-open interval semantics [start, + * end). + */ +export interface DoubleRange__Output { + /** + * start of the range (inclusive) + */ + 'start': (number); + /** + * end of the range (exclusive) + */ + 'end': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts new file mode 100644 index 000000000..564af9a0f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts @@ -0,0 +1,68 @@ +// Original file: deps/envoy-api/envoy/type/v3/percent.proto + + +// Original file: deps/envoy-api/envoy/type/v3/percent.proto + +/** + * Fraction percentages support several fixed denominator values. + */ +export enum _envoy_type_v3_FractionalPercent_DenominatorType { + /** + * 100. + * + * **Example**: 1/100 = 1%. + */ + HUNDRED = 0, + /** + * 10,000. + * + * **Example**: 1/10000 = 0.01%. + */ + TEN_THOUSAND = 1, + /** + * 1,000,000. + * + * **Example**: 1/1000000 = 0.0001%. + */ + MILLION = 2, +} + +/** + * A fractional percentage is used in cases in which for performance reasons performing floating + * point to integer conversions during randomness calculations is undesirable. The message includes + * both a numerator and denominator that together determine the final fractional value. + * + * * **Example**: 1/100 = 1%. + * * **Example**: 3/10000 = 0.03%. + */ +export interface FractionalPercent { + /** + * Specifies the numerator. Defaults to 0. + */ + 'numerator'?: (number); + /** + * Specifies the denominator. If the denominator specified is less than the numerator, the final + * fractional percentage is capped at 1 (100%). + */ + 'denominator'?: (_envoy_type_v3_FractionalPercent_DenominatorType | keyof typeof _envoy_type_v3_FractionalPercent_DenominatorType); +} + +/** + * A fractional percentage is used in cases in which for performance reasons performing floating + * point to integer conversions during randomness calculations is undesirable. The message includes + * both a numerator and denominator that together determine the final fractional value. + * + * * **Example**: 1/100 = 1%. + * * **Example**: 3/10000 = 0.03%. + */ +export interface FractionalPercent__Output { + /** + * Specifies the numerator. Defaults to 0. + */ + 'numerator': (number); + /** + * Specifies the denominator. If the denominator specified is less than the numerator, the final + * fractional percentage is capped at 1 (100%). + */ + 'denominator': (keyof typeof _envoy_type_v3_FractionalPercent_DenominatorType); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/Int32Range.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/Int32Range.ts new file mode 100644 index 000000000..826af6c38 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/Int32Range.ts @@ -0,0 +1,32 @@ +// Original file: deps/envoy-api/envoy/type/v3/range.proto + + +/** + * Specifies the int32 start and end of the range using half-open interval semantics [start, + * end). + */ +export interface Int32Range { + /** + * start of the range (inclusive) + */ + 'start'?: (number); + /** + * end of the range (exclusive) + */ + 'end'?: (number); +} + +/** + * Specifies the int32 start and end of the range using half-open interval semantics [start, + * end). + */ +export interface Int32Range__Output { + /** + * start of the range (inclusive) + */ + 'start': (number); + /** + * end of the range (exclusive) + */ + 'end': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/Int64Range.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/Int64Range.ts new file mode 100644 index 000000000..89ce7fb8d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/Int64Range.ts @@ -0,0 +1,33 @@ +// Original file: deps/envoy-api/envoy/type/v3/range.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * Specifies the int64 start and end of the range using half-open interval semantics [start, + * end). + */ +export interface Int64Range { + /** + * start of the range (inclusive) + */ + 'start'?: (number | string | Long); + /** + * end of the range (exclusive) + */ + 'end'?: (number | string | Long); +} + +/** + * Specifies the int64 start and end of the range using half-open interval semantics [start, + * end). + */ +export interface Int64Range__Output { + /** + * start of the range (inclusive) + */ + 'start': (string); + /** + * end of the range (exclusive) + */ + 'end': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/Percent.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/Percent.ts new file mode 100644 index 000000000..01d236c4e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/Percent.ts @@ -0,0 +1,16 @@ +// Original file: deps/envoy-api/envoy/type/v3/percent.proto + + +/** + * Identifies a percentage, in the range [0.0, 100.0]. + */ +export interface Percent { + 'value'?: (number | string); +} + +/** + * Identifies a percentage, in the range [0.0, 100.0]. + */ +export interface Percent__Output { + 'value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/SemanticVersion.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/SemanticVersion.ts new file mode 100644 index 000000000..3f714a766 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/SemanticVersion.ts @@ -0,0 +1,24 @@ +// Original file: deps/envoy-api/envoy/type/v3/semantic_version.proto + + +/** + * Envoy uses SemVer (https://semver.org/). Major/minor versions indicate + * expected behaviors and APIs, the patch version field is used only + * for security fixes and can be generally ignored. + */ +export interface SemanticVersion { + 'major_number'?: (number); + 'minor_number'?: (number); + 'patch'?: (number); +} + +/** + * Envoy uses SemVer (https://semver.org/). Major/minor versions indicate + * expected behaviors and APIs, the patch version field is used only + * for security fixes and can be generally ignored. + */ +export interface SemanticVersion__Output { + 'major_number': (number); + 'minor_number': (number); + 'patch': (number); +} diff --git a/packages/grpc-js-xds/src/generated/fault.ts b/packages/grpc-js-xds/src/generated/fault.ts new file mode 100644 index 000000000..896382e50 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/fault.ts @@ -0,0 +1,236 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + annotations: { + } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + route: { + v3: { + CorsPolicy: MessageTypeDefinition + Decorator: MessageTypeDefinition + DirectResponseAction: MessageTypeDefinition + FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition + HeaderMatcher: MessageTypeDefinition + HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition + QueryParameterMatcher: MessageTypeDefinition + RateLimit: MessageTypeDefinition + RedirectAction: MessageTypeDefinition + RetryPolicy: MessageTypeDefinition + Route: MessageTypeDefinition + RouteAction: MessageTypeDefinition + RouteMatch: MessageTypeDefinition + Tracing: MessageTypeDefinition + VirtualCluster: MessageTypeDefinition + VirtualHost: MessageTypeDefinition + WeightedCluster: MessageTypeDefinition + } + } + } + extensions: { + filters: { + common: { + fault: { + v3: { + FaultDelay: MessageTypeDefinition + FaultRateLimit: MessageTypeDefinition + } + } + } + http: { + fault: { + v3: { + FaultAbort: MessageTypeDefinition + HTTPFault: MessageTypeDefinition + } + } + } + } + } + type: { + matcher: { + v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition + ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition + } + } + metadata: { + v3: { + MetadataKey: MessageTypeDefinition + MetadataKind: MessageTypeDefinition + } + } + tracing: { + v3: { + CustomTag: MessageTypeDefinition + } + } + v3: { + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + Empty: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + ContextParams: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts b/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts new file mode 100644 index 000000000..2b6490be6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts @@ -0,0 +1,30 @@ +// Original file: deps/googleapis/google/api/http.proto + + +/** + * A custom pattern is used for defining custom HTTP verb. + */ +export interface CustomHttpPattern { + /** + * The name of this custom HTTP verb. + */ + 'kind'?: (string); + /** + * The path matched by this custom verb. + */ + 'path'?: (string); +} + +/** + * A custom pattern is used for defining custom HTTP verb. + */ +export interface CustomHttpPattern__Output { + /** + * The name of this custom HTTP verb. + */ + 'kind': (string); + /** + * The path matched by this custom verb. + */ + 'path': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/api/Http.ts b/packages/grpc-js-xds/src/generated/google/api/Http.ts new file mode 100644 index 000000000..e9b3cb309 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/api/Http.ts @@ -0,0 +1,49 @@ +// Original file: deps/googleapis/google/api/http.proto + +import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; + +/** + * Defines the HTTP configuration for an API service. It contains a list of + * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method + * to one or more HTTP REST API methods. + */ +export interface Http { + /** + * A list of HTTP configuration rules that apply to individual API methods. + * + * **NOTE:** All service configuration rules follow "last one wins" order. + */ + 'rules'?: (_google_api_HttpRule)[]; + /** + * When set to true, URL path parameters will be fully URI-decoded except in + * cases of single segment matches in reserved expansion, where "%2F" will be + * left encoded. + * + * The default behavior is to not decode RFC 6570 reserved characters in multi + * segment matches. + */ + 'fully_decode_reserved_expansion'?: (boolean); +} + +/** + * Defines the HTTP configuration for an API service. It contains a list of + * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method + * to one or more HTTP REST API methods. + */ +export interface Http__Output { + /** + * A list of HTTP configuration rules that apply to individual API methods. + * + * **NOTE:** All service configuration rules follow "last one wins" order. + */ + 'rules': (_google_api_HttpRule__Output)[]; + /** + * When set to true, URL path parameters will be fully URI-decoded except in + * cases of single segment matches in reserved expansion, where "%2F" will be + * left encoded. + * + * The default behavior is to not decode RFC 6570 reserved characters in multi + * segment matches. + */ + 'fully_decode_reserved_expansion': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts b/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts new file mode 100644 index 000000000..243a99f80 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts @@ -0,0 +1,680 @@ +// Original file: deps/googleapis/google/api/http.proto + +import type { CustomHttpPattern as _google_api_CustomHttpPattern, CustomHttpPattern__Output as _google_api_CustomHttpPattern__Output } from '../../google/api/CustomHttpPattern'; +import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; + +/** + * # gRPC Transcoding + * + * gRPC Transcoding is a feature for mapping between a gRPC method and one or + * more HTTP REST endpoints. It allows developers to build a single API service + * that supports both gRPC APIs and REST APIs. Many systems, including [Google + * APIs](https://github.com/googleapis/googleapis), + * [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC + * Gateway](https://github.com/grpc-ecosystem/grpc-gateway), + * and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature + * and use it for large scale production services. + * + * `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies + * how different portions of the gRPC request message are mapped to the URL + * path, URL query parameters, and HTTP request body. It also controls how the + * gRPC response message is mapped to the HTTP response body. `HttpRule` is + * typically specified as an `google.api.http` annotation on the gRPC method. + * + * Each mapping specifies a URL path template and an HTTP method. The path + * template may refer to one or more fields in the gRPC request message, as long + * as each field is a non-repeated field with a primitive (non-message) type. + * The path template controls how fields of the request message are mapped to + * the URL path. + * + * Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/{name=messages/*}" + * }; + * } + * } + * message GetMessageRequest { + * string name = 1; // Mapped to URL path. + * } + * message Message { + * string text = 1; // The resource content. + * } + * + * This enables an HTTP REST to gRPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` + * + * Any fields in the request message which are not bound by the path template + * automatically become HTTP query parameters if there is no HTTP request body. + * For example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get:"/v1/messages/{message_id}" + * }; + * } + * } + * message GetMessageRequest { + * message SubMessage { + * string subfield = 1; + * } + * string message_id = 1; // Mapped to URL path. + * int64 revision = 2; // Mapped to URL query parameter `revision`. + * SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. + * } + * + * This enables a HTTP JSON to RPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456?revision=2&sub.subfield=foo` | + * `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: + * "foo"))` + * + * Note that fields which are mapped to URL query parameters must have a + * primitive type or a repeated primitive type or a non-repeated message type. + * In the case of a repeated type, the parameter can be repeated in the URL + * as `...?param=A¶m=B`. In the case of a message type, each field of the + * message is mapped to a separate parameter, such as + * `...?foo.a=A&foo.b=B&foo.c=C`. + * + * For HTTP methods that allow a request body, the `body` field + * specifies the mapping. Consider a REST update method on the + * message resource collection: + * + * service Messaging { + * rpc UpdateMessage(UpdateMessageRequest) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "message" + * }; + * } + * } + * message UpdateMessageRequest { + * string message_id = 1; // mapped to the URL + * Message message = 2; // mapped to the body + * } + * + * The following HTTP JSON to RPC mapping is enabled, where the + * representation of the JSON in the request body is determined by + * protos JSON encoding: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" message { text: "Hi!" })` + * + * The special name `*` can be used in the body mapping to define that + * every field not bound by the path template should be mapped to the + * request body. This enables the following alternative definition of + * the update method: + * + * service Messaging { + * rpc UpdateMessage(Message) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "*" + * }; + * } + * } + * message Message { + * string message_id = 1; + * string text = 2; + * } + * + * + * The following HTTP JSON to RPC mapping is enabled: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" text: "Hi!")` + * + * Note that when using `*` in the body mapping, it is not possible to + * have HTTP parameters, as all fields not bound by the path end in + * the body. This makes this option more rarely used in practice when + * defining REST APIs. The common usage of `*` is in custom methods + * which don't use the URL at all for transferring data. + * + * It is possible to define multiple HTTP methods for one RPC by using + * the `additional_bindings` option. Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/messages/{message_id}" + * additional_bindings { + * get: "/v1/users/{user_id}/messages/{message_id}" + * } + * }; + * } + * } + * message GetMessageRequest { + * string message_id = 1; + * string user_id = 2; + * } + * + * This enables the following two alternative HTTP JSON to RPC mappings: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` + * `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: + * "123456")` + * + * ## Rules for HTTP mapping + * + * 1. Leaf request fields (recursive expansion nested messages in the request + * message) are classified into three categories: + * - Fields referred by the path template. They are passed via the URL path. + * - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP + * request body. + * - All other fields are passed via the URL query parameters, and the + * parameter name is the field path in the request message. A repeated + * field can be represented as multiple query parameters under the same + * name. + * 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields + * are passed via URL path and HTTP request body. + * 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all + * fields are passed via URL path and URL query parameters. + * + * ### Path template syntax + * + * Template = "/" Segments [ Verb ] ; + * Segments = Segment { "/" Segment } ; + * Segment = "*" | "**" | LITERAL | Variable ; + * Variable = "{" FieldPath [ "=" Segments ] "}" ; + * FieldPath = IDENT { "." IDENT } ; + * Verb = ":" LITERAL ; + * + * The syntax `*` matches a single URL path segment. The syntax `**` matches + * zero or more URL path segments, which must be the last part of the URL path + * except the `Verb`. + * + * The syntax `Variable` matches part of the URL path as specified by its + * template. A variable template must not contain other variables. If a variable + * matches a single path segment, its template may be omitted, e.g. `{var}` + * is equivalent to `{var=*}`. + * + * The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` + * contains any reserved character, such characters should be percent-encoded + * before the matching. + * + * If a variable contains exactly one path segment, such as `"{var}"` or + * `"{var=*}"`, when such a variable is expanded into a URL path on the client + * side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The + * server side does the reverse decoding. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{var}`. + * + * If a variable contains multiple path segments, such as `"{var=foo/*}"` + * or `"{var=**}"`, when such a variable is expanded into a URL path on the + * client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. + * The server side does the reverse decoding, except "%2F" and "%2f" are left + * unchanged. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{+var}`. + * + * ## Using gRPC API Service Configuration + * + * gRPC API Service Configuration (service config) is a configuration language + * for configuring a gRPC service to become a user-facing product. The + * service config is simply the YAML representation of the `google.api.Service` + * proto message. + * + * As an alternative to annotating your proto file, you can configure gRPC + * transcoding in your service config YAML files. You do this by specifying a + * `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same + * effect as the proto annotation. This can be particularly useful if you + * have a proto that is reused in multiple services. Note that any transcoding + * specified in the service config will override any matching transcoding + * configuration in the proto. + * + * Example: + * + * http: + * rules: + * # Selects a gRPC method and applies HttpRule to it. + * - selector: example.v1.Messaging.GetMessage + * get: /v1/messages/{message_id}/{sub.subfield} + * + * ## Special notes + * + * When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the + * proto to JSON conversion must follow the [proto3 + * specification](https://developers.google.com/protocol-buffers/docs/proto3#json). + * + * While the single segment variable follows the semantics of + * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String + * Expansion, the multi segment variable **does not** follow RFC 6570 Section + * 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion + * does not expand special characters like `?` and `#`, which would lead + * to invalid URLs. As the result, gRPC Transcoding uses a custom encoding + * for multi segment variables. + * + * The path variables **must not** refer to any repeated or mapped field, + * because client libraries are not capable of handling such variable expansion. + * + * The path variables **must not** capture the leading "/" character. The reason + * is that the most common use case "{var}" does not capture the leading "/" + * character. For consistency, all path variables must share the same behavior. + * + * Repeated message fields must not be mapped to URL query parameters, because + * no client library can support such complicated mapping. + * + * If an API needs to use a JSON array for request or response body, it can map + * the request or response body to a repeated field. However, some gRPC + * Transcoding implementations may not support this feature. + */ +export interface HttpRule { + /** + * Selects a method to which this rule applies. + * + * Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + */ + 'selector'?: (string); + /** + * Maps to HTTP GET. Used for listing and getting information about + * resources. + */ + 'get'?: (string); + /** + * Maps to HTTP PUT. Used for replacing a resource. + */ + 'put'?: (string); + /** + * Maps to HTTP POST. Used for creating a resource or performing an action. + */ + 'post'?: (string); + /** + * Maps to HTTP DELETE. Used for deleting a resource. + */ + 'delete'?: (string); + /** + * Maps to HTTP PATCH. Used for updating a resource. + */ + 'patch'?: (string); + /** + * The name of the request field whose value is mapped to the HTTP request + * body, or `*` for mapping all request fields not captured by the path + * pattern to the HTTP body, or omitted for not having any HTTP request body. + * + * NOTE: the referred field must be present at the top-level of the request + * message type. + */ + 'body'?: (string); + /** + * The custom pattern is used for specifying an HTTP method that is not + * included in the `pattern` field, such as HEAD, or "*" to leave the + * HTTP method unspecified for this rule. The wild-card rule is useful + * for services that provide content to Web (HTML) clients. + */ + 'custom'?: (_google_api_CustomHttpPattern | null); + /** + * Additional HTTP bindings for the selector. Nested bindings must + * not contain an `additional_bindings` field themselves (that is, + * the nesting may only be one level deep). + */ + 'additional_bindings'?: (_google_api_HttpRule)[]; + /** + * Optional. The name of the response field whose value is mapped to the HTTP + * response body. When omitted, the entire response message will be used + * as the HTTP response body. + * + * NOTE: The referred field must be present at the top-level of the response + * message type. + */ + 'response_body'?: (string); + /** + * Determines the URL pattern is matched by this rules. This pattern can be + * used with any of the {get|put|post|delete|patch} methods. A custom method + * can be defined using the 'custom' field. + */ + 'pattern'?: "get"|"put"|"post"|"delete"|"patch"|"custom"; +} + +/** + * # gRPC Transcoding + * + * gRPC Transcoding is a feature for mapping between a gRPC method and one or + * more HTTP REST endpoints. It allows developers to build a single API service + * that supports both gRPC APIs and REST APIs. Many systems, including [Google + * APIs](https://github.com/googleapis/googleapis), + * [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC + * Gateway](https://github.com/grpc-ecosystem/grpc-gateway), + * and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature + * and use it for large scale production services. + * + * `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies + * how different portions of the gRPC request message are mapped to the URL + * path, URL query parameters, and HTTP request body. It also controls how the + * gRPC response message is mapped to the HTTP response body. `HttpRule` is + * typically specified as an `google.api.http` annotation on the gRPC method. + * + * Each mapping specifies a URL path template and an HTTP method. The path + * template may refer to one or more fields in the gRPC request message, as long + * as each field is a non-repeated field with a primitive (non-message) type. + * The path template controls how fields of the request message are mapped to + * the URL path. + * + * Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/{name=messages/*}" + * }; + * } + * } + * message GetMessageRequest { + * string name = 1; // Mapped to URL path. + * } + * message Message { + * string text = 1; // The resource content. + * } + * + * This enables an HTTP REST to gRPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` + * + * Any fields in the request message which are not bound by the path template + * automatically become HTTP query parameters if there is no HTTP request body. + * For example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get:"/v1/messages/{message_id}" + * }; + * } + * } + * message GetMessageRequest { + * message SubMessage { + * string subfield = 1; + * } + * string message_id = 1; // Mapped to URL path. + * int64 revision = 2; // Mapped to URL query parameter `revision`. + * SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. + * } + * + * This enables a HTTP JSON to RPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456?revision=2&sub.subfield=foo` | + * `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: + * "foo"))` + * + * Note that fields which are mapped to URL query parameters must have a + * primitive type or a repeated primitive type or a non-repeated message type. + * In the case of a repeated type, the parameter can be repeated in the URL + * as `...?param=A¶m=B`. In the case of a message type, each field of the + * message is mapped to a separate parameter, such as + * `...?foo.a=A&foo.b=B&foo.c=C`. + * + * For HTTP methods that allow a request body, the `body` field + * specifies the mapping. Consider a REST update method on the + * message resource collection: + * + * service Messaging { + * rpc UpdateMessage(UpdateMessageRequest) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "message" + * }; + * } + * } + * message UpdateMessageRequest { + * string message_id = 1; // mapped to the URL + * Message message = 2; // mapped to the body + * } + * + * The following HTTP JSON to RPC mapping is enabled, where the + * representation of the JSON in the request body is determined by + * protos JSON encoding: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" message { text: "Hi!" })` + * + * The special name `*` can be used in the body mapping to define that + * every field not bound by the path template should be mapped to the + * request body. This enables the following alternative definition of + * the update method: + * + * service Messaging { + * rpc UpdateMessage(Message) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "*" + * }; + * } + * } + * message Message { + * string message_id = 1; + * string text = 2; + * } + * + * + * The following HTTP JSON to RPC mapping is enabled: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" text: "Hi!")` + * + * Note that when using `*` in the body mapping, it is not possible to + * have HTTP parameters, as all fields not bound by the path end in + * the body. This makes this option more rarely used in practice when + * defining REST APIs. The common usage of `*` is in custom methods + * which don't use the URL at all for transferring data. + * + * It is possible to define multiple HTTP methods for one RPC by using + * the `additional_bindings` option. Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/messages/{message_id}" + * additional_bindings { + * get: "/v1/users/{user_id}/messages/{message_id}" + * } + * }; + * } + * } + * message GetMessageRequest { + * string message_id = 1; + * string user_id = 2; + * } + * + * This enables the following two alternative HTTP JSON to RPC mappings: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` + * `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: + * "123456")` + * + * ## Rules for HTTP mapping + * + * 1. Leaf request fields (recursive expansion nested messages in the request + * message) are classified into three categories: + * - Fields referred by the path template. They are passed via the URL path. + * - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP + * request body. + * - All other fields are passed via the URL query parameters, and the + * parameter name is the field path in the request message. A repeated + * field can be represented as multiple query parameters under the same + * name. + * 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields + * are passed via URL path and HTTP request body. + * 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all + * fields are passed via URL path and URL query parameters. + * + * ### Path template syntax + * + * Template = "/" Segments [ Verb ] ; + * Segments = Segment { "/" Segment } ; + * Segment = "*" | "**" | LITERAL | Variable ; + * Variable = "{" FieldPath [ "=" Segments ] "}" ; + * FieldPath = IDENT { "." IDENT } ; + * Verb = ":" LITERAL ; + * + * The syntax `*` matches a single URL path segment. The syntax `**` matches + * zero or more URL path segments, which must be the last part of the URL path + * except the `Verb`. + * + * The syntax `Variable` matches part of the URL path as specified by its + * template. A variable template must not contain other variables. If a variable + * matches a single path segment, its template may be omitted, e.g. `{var}` + * is equivalent to `{var=*}`. + * + * The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` + * contains any reserved character, such characters should be percent-encoded + * before the matching. + * + * If a variable contains exactly one path segment, such as `"{var}"` or + * `"{var=*}"`, when such a variable is expanded into a URL path on the client + * side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The + * server side does the reverse decoding. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{var}`. + * + * If a variable contains multiple path segments, such as `"{var=foo/*}"` + * or `"{var=**}"`, when such a variable is expanded into a URL path on the + * client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. + * The server side does the reverse decoding, except "%2F" and "%2f" are left + * unchanged. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{+var}`. + * + * ## Using gRPC API Service Configuration + * + * gRPC API Service Configuration (service config) is a configuration language + * for configuring a gRPC service to become a user-facing product. The + * service config is simply the YAML representation of the `google.api.Service` + * proto message. + * + * As an alternative to annotating your proto file, you can configure gRPC + * transcoding in your service config YAML files. You do this by specifying a + * `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same + * effect as the proto annotation. This can be particularly useful if you + * have a proto that is reused in multiple services. Note that any transcoding + * specified in the service config will override any matching transcoding + * configuration in the proto. + * + * Example: + * + * http: + * rules: + * # Selects a gRPC method and applies HttpRule to it. + * - selector: example.v1.Messaging.GetMessage + * get: /v1/messages/{message_id}/{sub.subfield} + * + * ## Special notes + * + * When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the + * proto to JSON conversion must follow the [proto3 + * specification](https://developers.google.com/protocol-buffers/docs/proto3#json). + * + * While the single segment variable follows the semantics of + * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String + * Expansion, the multi segment variable **does not** follow RFC 6570 Section + * 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion + * does not expand special characters like `?` and `#`, which would lead + * to invalid URLs. As the result, gRPC Transcoding uses a custom encoding + * for multi segment variables. + * + * The path variables **must not** refer to any repeated or mapped field, + * because client libraries are not capable of handling such variable expansion. + * + * The path variables **must not** capture the leading "/" character. The reason + * is that the most common use case "{var}" does not capture the leading "/" + * character. For consistency, all path variables must share the same behavior. + * + * Repeated message fields must not be mapped to URL query parameters, because + * no client library can support such complicated mapping. + * + * If an API needs to use a JSON array for request or response body, it can map + * the request or response body to a repeated field. However, some gRPC + * Transcoding implementations may not support this feature. + */ +export interface HttpRule__Output { + /** + * Selects a method to which this rule applies. + * + * Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + */ + 'selector': (string); + /** + * Maps to HTTP GET. Used for listing and getting information about + * resources. + */ + 'get'?: (string); + /** + * Maps to HTTP PUT. Used for replacing a resource. + */ + 'put'?: (string); + /** + * Maps to HTTP POST. Used for creating a resource or performing an action. + */ + 'post'?: (string); + /** + * Maps to HTTP DELETE. Used for deleting a resource. + */ + 'delete'?: (string); + /** + * Maps to HTTP PATCH. Used for updating a resource. + */ + 'patch'?: (string); + /** + * The name of the request field whose value is mapped to the HTTP request + * body, or `*` for mapping all request fields not captured by the path + * pattern to the HTTP body, or omitted for not having any HTTP request body. + * + * NOTE: the referred field must be present at the top-level of the request + * message type. + */ + 'body': (string); + /** + * The custom pattern is used for specifying an HTTP method that is not + * included in the `pattern` field, such as HEAD, or "*" to leave the + * HTTP method unspecified for this rule. The wild-card rule is useful + * for services that provide content to Web (HTML) clients. + */ + 'custom'?: (_google_api_CustomHttpPattern__Output | null); + /** + * Additional HTTP bindings for the selector. Nested bindings must + * not contain an `additional_bindings` field themselves (that is, + * the nesting may only be one level deep). + */ + 'additional_bindings': (_google_api_HttpRule__Output)[]; + /** + * Optional. The name of the response field whose value is mapped to the HTTP + * response body. When omitted, the entire response message will be used + * as the HTTP response body. + * + * NOTE: The referred field must be present at the top-level of the response + * message type. + */ + 'response_body': (string); + /** + * Determines the URL pattern is matched by this rules. This pattern can be + * used with any of the {get|put|post|delete|patch} methods. A custom method + * can be defined using the 'custom' field. + */ + 'pattern': "get"|"put"|"post"|"delete"|"patch"|"custom"; +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Any.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Any.ts new file mode 100644 index 000000000..fcaa6724e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Any.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { AnyExtension } from '@grpc/proto-loader'; + +export type Any = AnyExtension | { + type_url: string; + value: Buffer | Uint8Array | string; +} + +export interface Any__Output { + 'type_url': (string); + 'value': (Buffer); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/BoolValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/BoolValue.ts new file mode 100644 index 000000000..86507eaf1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/BoolValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface BoolValue { + 'value'?: (boolean); +} + +export interface BoolValue__Output { + 'value': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/BytesValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/BytesValue.ts new file mode 100644 index 000000000..9cec76f71 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/BytesValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface BytesValue { + 'value'?: (Buffer | Uint8Array | string); +} + +export interface BytesValue__Output { + 'value': (Buffer); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/DescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/DescriptorProto.ts new file mode 100644 index 000000000..f729437f4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/DescriptorProto.ts @@ -0,0 +1,53 @@ +// Original file: null + +import type { FieldDescriptorProto as _google_protobuf_FieldDescriptorProto, FieldDescriptorProto__Output as _google_protobuf_FieldDescriptorProto__Output } from '../../google/protobuf/FieldDescriptorProto'; +import type { DescriptorProto as _google_protobuf_DescriptorProto, DescriptorProto__Output as _google_protobuf_DescriptorProto__Output } from '../../google/protobuf/DescriptorProto'; +import type { EnumDescriptorProto as _google_protobuf_EnumDescriptorProto, EnumDescriptorProto__Output as _google_protobuf_EnumDescriptorProto__Output } from '../../google/protobuf/EnumDescriptorProto'; +import type { MessageOptions as _google_protobuf_MessageOptions, MessageOptions__Output as _google_protobuf_MessageOptions__Output } from '../../google/protobuf/MessageOptions'; +import type { OneofDescriptorProto as _google_protobuf_OneofDescriptorProto, OneofDescriptorProto__Output as _google_protobuf_OneofDescriptorProto__Output } from '../../google/protobuf/OneofDescriptorProto'; + +export interface _google_protobuf_DescriptorProto_ExtensionRange { + 'start'?: (number); + 'end'?: (number); +} + +export interface _google_protobuf_DescriptorProto_ExtensionRange__Output { + 'start': (number); + 'end': (number); +} + +export interface _google_protobuf_DescriptorProto_ReservedRange { + 'start'?: (number); + 'end'?: (number); +} + +export interface _google_protobuf_DescriptorProto_ReservedRange__Output { + 'start': (number); + 'end': (number); +} + +export interface DescriptorProto { + 'name'?: (string); + 'field'?: (_google_protobuf_FieldDescriptorProto)[]; + 'nestedType'?: (_google_protobuf_DescriptorProto)[]; + 'enumType'?: (_google_protobuf_EnumDescriptorProto)[]; + 'extensionRange'?: (_google_protobuf_DescriptorProto_ExtensionRange)[]; + 'extension'?: (_google_protobuf_FieldDescriptorProto)[]; + 'options'?: (_google_protobuf_MessageOptions | null); + 'oneofDecl'?: (_google_protobuf_OneofDescriptorProto)[]; + 'reservedRange'?: (_google_protobuf_DescriptorProto_ReservedRange)[]; + 'reservedName'?: (string)[]; +} + +export interface DescriptorProto__Output { + 'name': (string); + 'field': (_google_protobuf_FieldDescriptorProto__Output)[]; + 'nestedType': (_google_protobuf_DescriptorProto__Output)[]; + 'enumType': (_google_protobuf_EnumDescriptorProto__Output)[]; + 'extensionRange': (_google_protobuf_DescriptorProto_ExtensionRange__Output)[]; + 'extension': (_google_protobuf_FieldDescriptorProto__Output)[]; + 'options': (_google_protobuf_MessageOptions__Output | null); + 'oneofDecl': (_google_protobuf_OneofDescriptorProto__Output)[]; + 'reservedRange': (_google_protobuf_DescriptorProto_ReservedRange__Output)[]; + 'reservedName': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/DoubleValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/DoubleValue.ts new file mode 100644 index 000000000..d70b303c2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/DoubleValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface DoubleValue { + 'value'?: (number | string); +} + +export interface DoubleValue__Output { + 'value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Duration.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Duration.ts new file mode 100644 index 000000000..8595377a0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Duration.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Duration { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface Duration__Output { + 'seconds': (string); + 'nanos': (number); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Empty.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Empty.ts new file mode 100644 index 000000000..f32c2a284 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Empty.ts @@ -0,0 +1,8 @@ +// Original file: null + + +export interface Empty { +} + +export interface Empty__Output { +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/EnumDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/EnumDescriptorProto.ts new file mode 100644 index 000000000..dc4c9673e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumDescriptorProto.ts @@ -0,0 +1,16 @@ +// Original file: null + +import type { EnumValueDescriptorProto as _google_protobuf_EnumValueDescriptorProto, EnumValueDescriptorProto__Output as _google_protobuf_EnumValueDescriptorProto__Output } from '../../google/protobuf/EnumValueDescriptorProto'; +import type { EnumOptions as _google_protobuf_EnumOptions, EnumOptions__Output as _google_protobuf_EnumOptions__Output } from '../../google/protobuf/EnumOptions'; + +export interface EnumDescriptorProto { + 'name'?: (string); + 'value'?: (_google_protobuf_EnumValueDescriptorProto)[]; + 'options'?: (_google_protobuf_EnumOptions | null); +} + +export interface EnumDescriptorProto__Output { + 'name': (string); + 'value': (_google_protobuf_EnumValueDescriptorProto__Output)[]; + 'options': (_google_protobuf_EnumOptions__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts new file mode 100644 index 000000000..777901a54 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts @@ -0,0 +1,18 @@ +// Original file: null + +import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation'; + +export interface EnumOptions { + 'allowAlias'?: (boolean); + 'deprecated'?: (boolean); + 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + '.udpa.annotations.enum_migrate'?: (_udpa_annotations_MigrateAnnotation | null); +} + +export interface EnumOptions__Output { + 'allowAlias': (boolean); + 'deprecated': (boolean); + 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + '.udpa.annotations.enum_migrate': (_udpa_annotations_MigrateAnnotation__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueDescriptorProto.ts new file mode 100644 index 000000000..7f8e57ea5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueDescriptorProto.ts @@ -0,0 +1,15 @@ +// Original file: null + +import type { EnumValueOptions as _google_protobuf_EnumValueOptions, EnumValueOptions__Output as _google_protobuf_EnumValueOptions__Output } from '../../google/protobuf/EnumValueOptions'; + +export interface EnumValueDescriptorProto { + 'name'?: (string); + 'number'?: (number); + 'options'?: (_google_protobuf_EnumValueOptions | null); +} + +export interface EnumValueDescriptorProto__Output { + 'name': (string); + 'number': (number); + 'options': (_google_protobuf_EnumValueOptions__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts new file mode 100644 index 000000000..9ba51ed60 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts @@ -0,0 +1,20 @@ +// Original file: null + +import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation'; + +export interface EnumValueOptions { + 'deprecated'?: (boolean); + 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + '.envoy.annotations.disallowed_by_default_enum'?: (boolean); + '.udpa.annotations.enum_value_migrate'?: (_udpa_annotations_MigrateAnnotation | null); + '.envoy.annotations.deprecated_at_minor_version_enum'?: (string); +} + +export interface EnumValueOptions__Output { + 'deprecated': (boolean); + 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + '.envoy.annotations.disallowed_by_default_enum': (boolean); + '.udpa.annotations.enum_value_migrate': (_udpa_annotations_MigrateAnnotation__Output | null); + '.envoy.annotations.deprecated_at_minor_version_enum': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts new file mode 100644 index 000000000..c511e2eff --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts @@ -0,0 +1,60 @@ +// Original file: null + +import type { FieldOptions as _google_protobuf_FieldOptions, FieldOptions__Output as _google_protobuf_FieldOptions__Output } from '../../google/protobuf/FieldOptions'; + +// Original file: null + +export enum _google_protobuf_FieldDescriptorProto_Label { + LABEL_OPTIONAL = 1, + LABEL_REQUIRED = 2, + LABEL_REPEATED = 3, +} + +// Original file: null + +export enum _google_protobuf_FieldDescriptorProto_Type { + TYPE_DOUBLE = 1, + TYPE_FLOAT = 2, + TYPE_INT64 = 3, + TYPE_UINT64 = 4, + TYPE_INT32 = 5, + TYPE_FIXED64 = 6, + TYPE_FIXED32 = 7, + TYPE_BOOL = 8, + TYPE_STRING = 9, + TYPE_GROUP = 10, + TYPE_MESSAGE = 11, + TYPE_BYTES = 12, + TYPE_UINT32 = 13, + TYPE_ENUM = 14, + TYPE_SFIXED32 = 15, + TYPE_SFIXED64 = 16, + TYPE_SINT32 = 17, + TYPE_SINT64 = 18, +} + +export interface FieldDescriptorProto { + 'name'?: (string); + 'extendee'?: (string); + 'number'?: (number); + 'label'?: (_google_protobuf_FieldDescriptorProto_Label | keyof typeof _google_protobuf_FieldDescriptorProto_Label); + 'type'?: (_google_protobuf_FieldDescriptorProto_Type | keyof typeof _google_protobuf_FieldDescriptorProto_Type); + 'typeName'?: (string); + 'defaultValue'?: (string); + 'options'?: (_google_protobuf_FieldOptions | null); + 'oneofIndex'?: (number); + 'jsonName'?: (string); +} + +export interface FieldDescriptorProto__Output { + 'name': (string); + 'extendee': (string); + 'number': (number); + 'label': (keyof typeof _google_protobuf_FieldDescriptorProto_Label); + 'type': (keyof typeof _google_protobuf_FieldDescriptorProto_Type); + 'typeName': (string); + 'defaultValue': (string); + 'options': (_google_protobuf_FieldOptions__Output | null); + 'oneofIndex': (number); + 'jsonName': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts new file mode 100644 index 000000000..d62db88d0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -0,0 +1,57 @@ +// Original file: null + +import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { FieldRules as _validate_FieldRules, FieldRules__Output as _validate_FieldRules__Output } from '../../validate/FieldRules'; +import type { FieldSecurityAnnotation as _udpa_annotations_FieldSecurityAnnotation, FieldSecurityAnnotation__Output as _udpa_annotations_FieldSecurityAnnotation__Output } from '../../udpa/annotations/FieldSecurityAnnotation'; +import type { FieldMigrateAnnotation as _udpa_annotations_FieldMigrateAnnotation, FieldMigrateAnnotation__Output as _udpa_annotations_FieldMigrateAnnotation__Output } from '../../udpa/annotations/FieldMigrateAnnotation'; +import type { FieldStatusAnnotation as _xds_annotations_v3_FieldStatusAnnotation, FieldStatusAnnotation__Output as _xds_annotations_v3_FieldStatusAnnotation__Output } from '../../xds/annotations/v3/FieldStatusAnnotation'; + +// Original file: null + +export enum _google_protobuf_FieldOptions_CType { + STRING = 0, + CORD = 1, + STRING_PIECE = 2, +} + +// Original file: null + +export enum _google_protobuf_FieldOptions_JSType { + JS_NORMAL = 0, + JS_STRING = 1, + JS_NUMBER = 2, +} + +export interface FieldOptions { + 'ctype'?: (_google_protobuf_FieldOptions_CType | keyof typeof _google_protobuf_FieldOptions_CType); + 'packed'?: (boolean); + 'deprecated'?: (boolean); + 'lazy'?: (boolean); + 'jstype'?: (_google_protobuf_FieldOptions_JSType | keyof typeof _google_protobuf_FieldOptions_JSType); + 'weak'?: (boolean); + 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + '.validate.rules'?: (_validate_FieldRules | null); + '.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation | null); + '.udpa.annotations.sensitive'?: (boolean); + '.envoy.annotations.deprecated_at_minor_version'?: (string); + '.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation | null); + '.envoy.annotations.disallowed_by_default'?: (boolean); + '.xds.annotations.v3.field_status'?: (_xds_annotations_v3_FieldStatusAnnotation | null); +} + +export interface FieldOptions__Output { + 'ctype': (keyof typeof _google_protobuf_FieldOptions_CType); + 'packed': (boolean); + 'deprecated': (boolean); + 'lazy': (boolean); + 'jstype': (keyof typeof _google_protobuf_FieldOptions_JSType); + 'weak': (boolean); + 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + '.validate.rules': (_validate_FieldRules__Output | null); + '.udpa.annotations.security': (_udpa_annotations_FieldSecurityAnnotation__Output | null); + '.udpa.annotations.sensitive': (boolean); + '.envoy.annotations.deprecated_at_minor_version': (string); + '.udpa.annotations.field_migrate': (_udpa_annotations_FieldMigrateAnnotation__Output | null); + '.envoy.annotations.disallowed_by_default': (boolean); + '.xds.annotations.v3.field_status': (_xds_annotations_v3_FieldStatusAnnotation__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorProto.ts new file mode 100644 index 000000000..b723da7c0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorProto.ts @@ -0,0 +1,38 @@ +// Original file: null + +import type { DescriptorProto as _google_protobuf_DescriptorProto, DescriptorProto__Output as _google_protobuf_DescriptorProto__Output } from '../../google/protobuf/DescriptorProto'; +import type { EnumDescriptorProto as _google_protobuf_EnumDescriptorProto, EnumDescriptorProto__Output as _google_protobuf_EnumDescriptorProto__Output } from '../../google/protobuf/EnumDescriptorProto'; +import type { ServiceDescriptorProto as _google_protobuf_ServiceDescriptorProto, ServiceDescriptorProto__Output as _google_protobuf_ServiceDescriptorProto__Output } from '../../google/protobuf/ServiceDescriptorProto'; +import type { FieldDescriptorProto as _google_protobuf_FieldDescriptorProto, FieldDescriptorProto__Output as _google_protobuf_FieldDescriptorProto__Output } from '../../google/protobuf/FieldDescriptorProto'; +import type { FileOptions as _google_protobuf_FileOptions, FileOptions__Output as _google_protobuf_FileOptions__Output } from '../../google/protobuf/FileOptions'; +import type { SourceCodeInfo as _google_protobuf_SourceCodeInfo, SourceCodeInfo__Output as _google_protobuf_SourceCodeInfo__Output } from '../../google/protobuf/SourceCodeInfo'; + +export interface FileDescriptorProto { + 'name'?: (string); + 'package'?: (string); + 'dependency'?: (string)[]; + 'messageType'?: (_google_protobuf_DescriptorProto)[]; + 'enumType'?: (_google_protobuf_EnumDescriptorProto)[]; + 'service'?: (_google_protobuf_ServiceDescriptorProto)[]; + 'extension'?: (_google_protobuf_FieldDescriptorProto)[]; + 'options'?: (_google_protobuf_FileOptions | null); + 'sourceCodeInfo'?: (_google_protobuf_SourceCodeInfo | null); + 'publicDependency'?: (number)[]; + 'weakDependency'?: (number)[]; + 'syntax'?: (string); +} + +export interface FileDescriptorProto__Output { + 'name': (string); + 'package': (string); + 'dependency': (string)[]; + 'messageType': (_google_protobuf_DescriptorProto__Output)[]; + 'enumType': (_google_protobuf_EnumDescriptorProto__Output)[]; + 'service': (_google_protobuf_ServiceDescriptorProto__Output)[]; + 'extension': (_google_protobuf_FieldDescriptorProto__Output)[]; + 'options': (_google_protobuf_FileOptions__Output | null); + 'sourceCodeInfo': (_google_protobuf_SourceCodeInfo__Output | null); + 'publicDependency': (number)[]; + 'weakDependency': (number)[]; + 'syntax': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorSet.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorSet.ts new file mode 100644 index 000000000..74ded2471 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorSet.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { FileDescriptorProto as _google_protobuf_FileDescriptorProto, FileDescriptorProto__Output as _google_protobuf_FileDescriptorProto__Output } from '../../google/protobuf/FileDescriptorProto'; + +export interface FileDescriptorSet { + 'file'?: (_google_protobuf_FileDescriptorProto)[]; +} + +export interface FileDescriptorSet__Output { + 'file': (_google_protobuf_FileDescriptorProto__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts new file mode 100644 index 000000000..48a376cd0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts @@ -0,0 +1,56 @@ +// Original file: null + +import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { FileMigrateAnnotation as _udpa_annotations_FileMigrateAnnotation, FileMigrateAnnotation__Output as _udpa_annotations_FileMigrateAnnotation__Output } from '../../udpa/annotations/FileMigrateAnnotation'; +import type { StatusAnnotation as _udpa_annotations_StatusAnnotation, StatusAnnotation__Output as _udpa_annotations_StatusAnnotation__Output } from '../../udpa/annotations/StatusAnnotation'; +import type { FileStatusAnnotation as _xds_annotations_v3_FileStatusAnnotation, FileStatusAnnotation__Output as _xds_annotations_v3_FileStatusAnnotation__Output } from '../../xds/annotations/v3/FileStatusAnnotation'; + +// Original file: null + +export enum _google_protobuf_FileOptions_OptimizeMode { + SPEED = 1, + CODE_SIZE = 2, + LITE_RUNTIME = 3, +} + +export interface FileOptions { + 'javaPackage'?: (string); + 'javaOuterClassname'?: (string); + 'optimizeFor'?: (_google_protobuf_FileOptions_OptimizeMode | keyof typeof _google_protobuf_FileOptions_OptimizeMode); + 'javaMultipleFiles'?: (boolean); + 'goPackage'?: (string); + 'ccGenericServices'?: (boolean); + 'javaGenericServices'?: (boolean); + 'pyGenericServices'?: (boolean); + 'javaGenerateEqualsAndHash'?: (boolean); + 'deprecated'?: (boolean); + 'javaStringCheckUtf8'?: (boolean); + 'ccEnableArenas'?: (boolean); + 'objcClassPrefix'?: (string); + 'csharpNamespace'?: (string); + 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + '.udpa.annotations.file_migrate'?: (_udpa_annotations_FileMigrateAnnotation | null); + '.udpa.annotations.file_status'?: (_udpa_annotations_StatusAnnotation | null); + '.xds.annotations.v3.file_status'?: (_xds_annotations_v3_FileStatusAnnotation | null); +} + +export interface FileOptions__Output { + 'javaPackage': (string); + 'javaOuterClassname': (string); + 'optimizeFor': (keyof typeof _google_protobuf_FileOptions_OptimizeMode); + 'javaMultipleFiles': (boolean); + 'goPackage': (string); + 'ccGenericServices': (boolean); + 'javaGenericServices': (boolean); + 'pyGenericServices': (boolean); + 'javaGenerateEqualsAndHash': (boolean); + 'deprecated': (boolean); + 'javaStringCheckUtf8': (boolean); + 'ccEnableArenas': (boolean); + 'objcClassPrefix': (string); + 'csharpNamespace': (string); + 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + '.udpa.annotations.file_migrate': (_udpa_annotations_FileMigrateAnnotation__Output | null); + '.udpa.annotations.file_status': (_udpa_annotations_StatusAnnotation__Output | null); + '.xds.annotations.v3.file_status': (_xds_annotations_v3_FileStatusAnnotation__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FloatValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FloatValue.ts new file mode 100644 index 000000000..54a655fbb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FloatValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface FloatValue { + 'value'?: (number | string); +} + +export interface FloatValue__Output { + 'value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/GeneratedCodeInfo.ts b/packages/grpc-js-xds/src/generated/google/protobuf/GeneratedCodeInfo.ts new file mode 100644 index 000000000..019fb0e15 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/GeneratedCodeInfo.ts @@ -0,0 +1,24 @@ +// Original file: null + + +export interface _google_protobuf_GeneratedCodeInfo_Annotation { + 'path'?: (number)[]; + 'sourceFile'?: (string); + 'begin'?: (number); + 'end'?: (number); +} + +export interface _google_protobuf_GeneratedCodeInfo_Annotation__Output { + 'path': (number)[]; + 'sourceFile': (string); + 'begin': (number); + 'end': (number); +} + +export interface GeneratedCodeInfo { + 'annotation'?: (_google_protobuf_GeneratedCodeInfo_Annotation)[]; +} + +export interface GeneratedCodeInfo__Output { + 'annotation': (_google_protobuf_GeneratedCodeInfo_Annotation__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Int32Value.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Int32Value.ts new file mode 100644 index 000000000..ec4eeb7ec --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Int32Value.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface Int32Value { + 'value'?: (number); +} + +export interface Int32Value__Output { + 'value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Int64Value.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Int64Value.ts new file mode 100644 index 000000000..f7375196d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Int64Value.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Int64Value { + 'value'?: (number | string | Long); +} + +export interface Int64Value__Output { + 'value': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/ListValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/ListValue.ts new file mode 100644 index 000000000..fa762b9ba --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/ListValue.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { Value as _google_protobuf_Value, Value__Output as _google_protobuf_Value__Output } from '../../google/protobuf/Value'; + +export interface ListValue { + 'values'?: (_google_protobuf_Value)[]; +} + +export interface ListValue__Output { + 'values': (_google_protobuf_Value__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts new file mode 100644 index 000000000..71d8c855b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts @@ -0,0 +1,30 @@ +// Original file: null + +import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { VersioningAnnotation as _udpa_annotations_VersioningAnnotation, VersioningAnnotation__Output as _udpa_annotations_VersioningAnnotation__Output } from '../../udpa/annotations/VersioningAnnotation'; +import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation'; +import type { MessageStatusAnnotation as _xds_annotations_v3_MessageStatusAnnotation, MessageStatusAnnotation__Output as _xds_annotations_v3_MessageStatusAnnotation__Output } from '../../xds/annotations/v3/MessageStatusAnnotation'; + +export interface MessageOptions { + 'messageSetWireFormat'?: (boolean); + 'noStandardDescriptorAccessor'?: (boolean); + 'deprecated'?: (boolean); + 'mapEntry'?: (boolean); + 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + '.validate.disabled'?: (boolean); + '.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation | null); + '.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation | null); + '.xds.annotations.v3.message_status'?: (_xds_annotations_v3_MessageStatusAnnotation | null); +} + +export interface MessageOptions__Output { + 'messageSetWireFormat': (boolean); + 'noStandardDescriptorAccessor': (boolean); + 'deprecated': (boolean); + 'mapEntry': (boolean); + 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + '.validate.disabled': (boolean); + '.udpa.annotations.versioning': (_udpa_annotations_VersioningAnnotation__Output | null); + '.udpa.annotations.message_migrate': (_udpa_annotations_MigrateAnnotation__Output | null); + '.xds.annotations.v3.message_status': (_xds_annotations_v3_MessageStatusAnnotation__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/MethodDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/MethodDescriptorProto.ts new file mode 100644 index 000000000..c76c0ea23 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MethodDescriptorProto.ts @@ -0,0 +1,21 @@ +// Original file: null + +import type { MethodOptions as _google_protobuf_MethodOptions, MethodOptions__Output as _google_protobuf_MethodOptions__Output } from '../../google/protobuf/MethodOptions'; + +export interface MethodDescriptorProto { + 'name'?: (string); + 'inputType'?: (string); + 'outputType'?: (string); + 'options'?: (_google_protobuf_MethodOptions | null); + 'clientStreaming'?: (boolean); + 'serverStreaming'?: (boolean); +} + +export interface MethodDescriptorProto__Output { + 'name': (string); + 'inputType': (string); + 'outputType': (string); + 'options': (_google_protobuf_MethodOptions__Output | null); + 'clientStreaming': (boolean); + 'serverStreaming': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts new file mode 100644 index 000000000..5f81f0dd9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts @@ -0,0 +1,16 @@ +// Original file: null + +import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; + +export interface MethodOptions { + 'deprecated'?: (boolean); + 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + '.google.api.http'?: (_google_api_HttpRule | null); +} + +export interface MethodOptions__Output { + 'deprecated': (boolean); + 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + '.google.api.http': (_google_api_HttpRule__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/NullValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/NullValue.ts new file mode 100644 index 000000000..377aab885 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/NullValue.ts @@ -0,0 +1,5 @@ +// Original file: null + +export enum NullValue { + NULL_VALUE = 0, +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/OneofDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/OneofDescriptorProto.ts new file mode 100644 index 000000000..636f13ed4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/OneofDescriptorProto.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { OneofOptions as _google_protobuf_OneofOptions, OneofOptions__Output as _google_protobuf_OneofOptions__Output } from '../../google/protobuf/OneofOptions'; + +export interface OneofDescriptorProto { + 'name'?: (string); + 'options'?: (_google_protobuf_OneofOptions | null); +} + +export interface OneofDescriptorProto__Output { + 'name': (string); + 'options': (_google_protobuf_OneofOptions__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/OneofOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/OneofOptions.ts new file mode 100644 index 000000000..b54ecb0b1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/OneofOptions.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; + +export interface OneofOptions { + 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + '.validate.required'?: (boolean); +} + +export interface OneofOptions__Output { + 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + '.validate.required': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/ServiceDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/ServiceDescriptorProto.ts new file mode 100644 index 000000000..40c9263ea --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/ServiceDescriptorProto.ts @@ -0,0 +1,16 @@ +// Original file: null + +import type { MethodDescriptorProto as _google_protobuf_MethodDescriptorProto, MethodDescriptorProto__Output as _google_protobuf_MethodDescriptorProto__Output } from '../../google/protobuf/MethodDescriptorProto'; +import type { ServiceOptions as _google_protobuf_ServiceOptions, ServiceOptions__Output as _google_protobuf_ServiceOptions__Output } from '../../google/protobuf/ServiceOptions'; + +export interface ServiceDescriptorProto { + 'name'?: (string); + 'method'?: (_google_protobuf_MethodDescriptorProto)[]; + 'options'?: (_google_protobuf_ServiceOptions | null); +} + +export interface ServiceDescriptorProto__Output { + 'name': (string); + 'method': (_google_protobuf_MethodDescriptorProto__Output)[]; + 'options': (_google_protobuf_ServiceOptions__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/ServiceOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/ServiceOptions.ts new file mode 100644 index 000000000..4899506a4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/ServiceOptions.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; + +export interface ServiceOptions { + 'deprecated'?: (boolean); + 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; +} + +export interface ServiceOptions__Output { + 'deprecated': (boolean); + 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/SourceCodeInfo.ts b/packages/grpc-js-xds/src/generated/google/protobuf/SourceCodeInfo.ts new file mode 100644 index 000000000..d30e59b4f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/SourceCodeInfo.ts @@ -0,0 +1,26 @@ +// Original file: null + + +export interface _google_protobuf_SourceCodeInfo_Location { + 'path'?: (number)[]; + 'span'?: (number)[]; + 'leadingComments'?: (string); + 'trailingComments'?: (string); + 'leadingDetachedComments'?: (string)[]; +} + +export interface _google_protobuf_SourceCodeInfo_Location__Output { + 'path': (number)[]; + 'span': (number)[]; + 'leadingComments': (string); + 'trailingComments': (string); + 'leadingDetachedComments': (string)[]; +} + +export interface SourceCodeInfo { + 'location'?: (_google_protobuf_SourceCodeInfo_Location)[]; +} + +export interface SourceCodeInfo__Output { + 'location': (_google_protobuf_SourceCodeInfo_Location__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/StringValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/StringValue.ts new file mode 100644 index 000000000..673090e3f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/StringValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface StringValue { + 'value'?: (string); +} + +export interface StringValue__Output { + 'value': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Struct.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Struct.ts new file mode 100644 index 000000000..41b79eab3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Struct.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { Value as _google_protobuf_Value, Value__Output as _google_protobuf_Value__Output } from '../../google/protobuf/Value'; + +export interface Struct { + 'fields'?: ({[key: string]: _google_protobuf_Value}); +} + +export interface Struct__Output { + 'fields': ({[key: string]: _google_protobuf_Value__Output}); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Timestamp.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Timestamp.ts new file mode 100644 index 000000000..ceaa32b5f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Timestamp.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Timestamp { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface Timestamp__Output { + 'seconds': (string); + 'nanos': (number); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/UInt32Value.ts b/packages/grpc-js-xds/src/generated/google/protobuf/UInt32Value.ts new file mode 100644 index 000000000..973ab34a5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/UInt32Value.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface UInt32Value { + 'value'?: (number); +} + +export interface UInt32Value__Output { + 'value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/UInt64Value.ts b/packages/grpc-js-xds/src/generated/google/protobuf/UInt64Value.ts new file mode 100644 index 000000000..7a85c39ce --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/UInt64Value.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface UInt64Value { + 'value'?: (number | string | Long); +} + +export interface UInt64Value__Output { + 'value': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/UninterpretedOption.ts b/packages/grpc-js-xds/src/generated/google/protobuf/UninterpretedOption.ts new file mode 100644 index 000000000..6e9fc275b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/UninterpretedOption.ts @@ -0,0 +1,33 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface _google_protobuf_UninterpretedOption_NamePart { + 'namePart'?: (string); + 'isExtension'?: (boolean); +} + +export interface _google_protobuf_UninterpretedOption_NamePart__Output { + 'namePart': (string); + 'isExtension': (boolean); +} + +export interface UninterpretedOption { + 'name'?: (_google_protobuf_UninterpretedOption_NamePart)[]; + 'identifierValue'?: (string); + 'positiveIntValue'?: (number | string | Long); + 'negativeIntValue'?: (number | string | Long); + 'doubleValue'?: (number | string); + 'stringValue'?: (Buffer | Uint8Array | string); + 'aggregateValue'?: (string); +} + +export interface UninterpretedOption__Output { + 'name': (_google_protobuf_UninterpretedOption_NamePart__Output)[]; + 'identifierValue': (string); + 'positiveIntValue': (string); + 'negativeIntValue': (string); + 'doubleValue': (number); + 'stringValue': (Buffer); + 'aggregateValue': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts new file mode 100644 index 000000000..b1a942a56 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts @@ -0,0 +1,25 @@ +// Original file: null + +import type { NullValue as _google_protobuf_NullValue } from '../../google/protobuf/NullValue'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../google/protobuf/Struct'; +import type { ListValue as _google_protobuf_ListValue, ListValue__Output as _google_protobuf_ListValue__Output } from '../../google/protobuf/ListValue'; + +export interface Value { + 'nullValue'?: (_google_protobuf_NullValue | keyof typeof _google_protobuf_NullValue); + 'numberValue'?: (number | string); + 'stringValue'?: (string); + 'boolValue'?: (boolean); + 'structValue'?: (_google_protobuf_Struct | null); + 'listValue'?: (_google_protobuf_ListValue | null); + 'kind'?: "nullValue"|"numberValue"|"stringValue"|"boolValue"|"structValue"|"listValue"; +} + +export interface Value__Output { + 'nullValue'?: (keyof typeof _google_protobuf_NullValue); + 'numberValue'?: (number); + 'stringValue'?: (string); + 'boolValue'?: (boolean); + 'structValue'?: (_google_protobuf_Struct__Output | null); + 'listValue'?: (_google_protobuf_ListValue__Output | null); + 'kind': "nullValue"|"numberValue"|"stringValue"|"boolValue"|"structValue"|"listValue"; +} diff --git a/packages/grpc-js-xds/src/generated/google/rpc/Status.ts b/packages/grpc-js-xds/src/generated/google/rpc/Status.ts new file mode 100644 index 000000000..4ce45b6a9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/rpc/Status.ts @@ -0,0 +1,57 @@ +// Original file: deps/googleapis/google/rpc/status.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../google/protobuf/Any'; + +/** + * The `Status` type defines a logical error model that is suitable for + * different programming environments, including REST APIs and RPC APIs. It is + * used by [gRPC](https://github.com/grpc). Each `Status` message contains + * three pieces of data: error code, error message, and error details. + * + * You can find out more about this error model and how to work with it in the + * [API Design Guide](https://cloud.google.com/apis/design/errors). + */ +export interface Status { + /** + * The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + 'code'?: (number); + /** + * A developer-facing error message, which should be in English. Any + * user-facing error message should be localized and sent in the + * [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + */ + 'message'?: (string); + /** + * A list of messages that carry the error details. There is a common set of + * message types for APIs to use. + */ + 'details'?: (_google_protobuf_Any)[]; +} + +/** + * The `Status` type defines a logical error model that is suitable for + * different programming environments, including REST APIs and RPC APIs. It is + * used by [gRPC](https://github.com/grpc). Each `Status` message contains + * three pieces of data: error code, error message, and error details. + * + * You can find out more about this error model and how to work with it in the + * [API Design Guide](https://cloud.google.com/apis/design/errors). + */ +export interface Status__Output { + /** + * The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + 'code': (number); + /** + * A developer-facing error message, which should be in English. Any + * user-facing error message should be localized and sent in the + * [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + */ + 'message': (string); + /** + * A list of messages that carry the error details. There is a common set of + * message types for APIs to use. + */ + 'details': (_google_protobuf_Any__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/http_connection_manager.ts b/packages/grpc-js-xds/src/generated/http_connection_manager.ts new file mode 100644 index 000000000..137dcd45a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/http_connection_manager.ts @@ -0,0 +1,282 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + annotations: { + } + config: { + accesslog: { + v3: { + AccessLog: MessageTypeDefinition + AccessLogFilter: MessageTypeDefinition + AndFilter: MessageTypeDefinition + ComparisonFilter: MessageTypeDefinition + DurationFilter: MessageTypeDefinition + ExtensionFilter: MessageTypeDefinition + GrpcStatusFilter: MessageTypeDefinition + HeaderFilter: MessageTypeDefinition + MetadataFilter: MessageTypeDefinition + NotHealthCheckFilter: MessageTypeDefinition + OrFilter: MessageTypeDefinition + ResponseFlagFilter: MessageTypeDefinition + RuntimeFilter: MessageTypeDefinition + StatusCodeFilter: MessageTypeDefinition + TraceableFilter: MessageTypeDefinition + } + } + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + GrpcProtocolOptions: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + Http1ProtocolOptions: MessageTypeDefinition + Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition + HttpProtocolOptions: MessageTypeDefinition + HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + SubstitutionFormatString: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TcpProtocolOptions: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + UpstreamHttpProtocolOptions: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + route: { + v3: { + ClusterSpecifierPlugin: MessageTypeDefinition + CorsPolicy: MessageTypeDefinition + Decorator: MessageTypeDefinition + DirectResponseAction: MessageTypeDefinition + FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition + HeaderMatcher: MessageTypeDefinition + HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition + QueryParameterMatcher: MessageTypeDefinition + RateLimit: MessageTypeDefinition + RedirectAction: MessageTypeDefinition + RetryPolicy: MessageTypeDefinition + Route: MessageTypeDefinition + RouteAction: MessageTypeDefinition + RouteConfiguration: MessageTypeDefinition + RouteMatch: MessageTypeDefinition + ScopedRouteConfiguration: MessageTypeDefinition + Tracing: MessageTypeDefinition + Vhds: MessageTypeDefinition + VirtualCluster: MessageTypeDefinition + VirtualHost: MessageTypeDefinition + WeightedCluster: MessageTypeDefinition + } + } + trace: { + v3: { + Tracing: MessageTypeDefinition + } + } + } + extensions: { + filters: { + network: { + http_connection_manager: { + v3: { + EnvoyMobileHttpConnectionManager: MessageTypeDefinition + HttpConnectionManager: MessageTypeDefinition + HttpFilter: MessageTypeDefinition + LocalReplyConfig: MessageTypeDefinition + Rds: MessageTypeDefinition + RequestIDExtension: MessageTypeDefinition + ResponseMapper: MessageTypeDefinition + ScopedRds: MessageTypeDefinition + ScopedRouteConfigurationsList: MessageTypeDefinition + ScopedRoutes: MessageTypeDefinition + } + } + } + } + } + type: { + http: { + v3: { + PathTransformation: MessageTypeDefinition + } + } + matcher: { + v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition + ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition + } + } + metadata: { + v3: { + MetadataKey: MessageTypeDefinition + MetadataKind: MessageTypeDefinition + } + } + tracing: { + v3: { + CustomTag: MessageTypeDefinition + } + } + v3: { + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + Empty: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FieldSecurityAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + ContextParams: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/listener.ts b/packages/grpc-js-xds/src/generated/listener.ts new file mode 100644 index 000000000..b92353ab1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/listener.ts @@ -0,0 +1,265 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + annotations: { + } + config: { + accesslog: { + v3: { + AccessLog: MessageTypeDefinition + AccessLogFilter: MessageTypeDefinition + AndFilter: MessageTypeDefinition + ComparisonFilter: MessageTypeDefinition + DurationFilter: MessageTypeDefinition + ExtensionFilter: MessageTypeDefinition + GrpcStatusFilter: MessageTypeDefinition + HeaderFilter: MessageTypeDefinition + MetadataFilter: MessageTypeDefinition + NotHealthCheckFilter: MessageTypeDefinition + OrFilter: MessageTypeDefinition + ResponseFlagFilter: MessageTypeDefinition + RuntimeFilter: MessageTypeDefinition + StatusCodeFilter: MessageTypeDefinition + TraceableFilter: MessageTypeDefinition + } + } + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + GrpcProtocolOptions: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + Http1ProtocolOptions: MessageTypeDefinition + Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition + HttpProtocolOptions: MessageTypeDefinition + HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TcpProtocolOptions: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + UdpSocketConfig: MessageTypeDefinition + UpstreamHttpProtocolOptions: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + listener: { + v3: { + ActiveRawUdpListenerConfig: MessageTypeDefinition + ApiListener: MessageTypeDefinition + Filter: MessageTypeDefinition + FilterChain: MessageTypeDefinition + FilterChainMatch: MessageTypeDefinition + Listener: MessageTypeDefinition + ListenerCollection: MessageTypeDefinition + ListenerFilter: MessageTypeDefinition + ListenerFilterChainMatchPredicate: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + UdpListenerConfig: MessageTypeDefinition + } + } + route: { + v3: { + CorsPolicy: MessageTypeDefinition + Decorator: MessageTypeDefinition + DirectResponseAction: MessageTypeDefinition + FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition + HeaderMatcher: MessageTypeDefinition + HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition + QueryParameterMatcher: MessageTypeDefinition + RateLimit: MessageTypeDefinition + RedirectAction: MessageTypeDefinition + RetryPolicy: MessageTypeDefinition + Route: MessageTypeDefinition + RouteAction: MessageTypeDefinition + RouteMatch: MessageTypeDefinition + Tracing: MessageTypeDefinition + VirtualCluster: MessageTypeDefinition + VirtualHost: MessageTypeDefinition + WeightedCluster: MessageTypeDefinition + } + } + } + type: { + matcher: { + v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition + ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition + } + } + metadata: { + v3: { + MetadataKey: MessageTypeDefinition + MetadataKind: MessageTypeDefinition + } + } + tracing: { + v3: { + CustomTag: MessageTypeDefinition + } + } + v3: { + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + Empty: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FieldSecurityAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + CollectionEntry: MessageTypeDefinition + ContextParams: MessageTypeDefinition + ResourceLocator: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/lrs.ts b/packages/grpc-js-xds/src/generated/lrs.ts new file mode 100644 index 000000000..e57d6c249 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/lrs.ts @@ -0,0 +1,172 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { LoadReportingServiceClient as _envoy_service_load_stats_v3_LoadReportingServiceClient, LoadReportingServiceDefinition as _envoy_service_load_stats_v3_LoadReportingServiceDefinition } from './envoy/service/load_stats/v3/LoadReportingService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + annotations: { + } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + endpoint: { + v3: { + ClusterStats: MessageTypeDefinition + EndpointLoadMetricStats: MessageTypeDefinition + UpstreamEndpointStats: MessageTypeDefinition + UpstreamLocalityStats: MessageTypeDefinition + } + } + } + service: { + load_stats: { + v3: { + LoadReportingService: SubtypeConstructor & { service: _envoy_service_load_stats_v3_LoadReportingServiceDefinition } + LoadStatsRequest: MessageTypeDefinition + LoadStatsResponse: MessageTypeDefinition + } + } + } + type: { + v3: { + FractionalPercent: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + ContextParams: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/route.ts b/packages/grpc-js-xds/src/generated/route.ts new file mode 100644 index 000000000..d6485bcd7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/route.ts @@ -0,0 +1,219 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + annotations: { + } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + route: { + v3: { + ClusterSpecifierPlugin: MessageTypeDefinition + CorsPolicy: MessageTypeDefinition + Decorator: MessageTypeDefinition + DirectResponseAction: MessageTypeDefinition + FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition + HeaderMatcher: MessageTypeDefinition + HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition + QueryParameterMatcher: MessageTypeDefinition + RateLimit: MessageTypeDefinition + RedirectAction: MessageTypeDefinition + RetryPolicy: MessageTypeDefinition + Route: MessageTypeDefinition + RouteAction: MessageTypeDefinition + RouteConfiguration: MessageTypeDefinition + RouteMatch: MessageTypeDefinition + Tracing: MessageTypeDefinition + Vhds: MessageTypeDefinition + VirtualCluster: MessageTypeDefinition + VirtualHost: MessageTypeDefinition + WeightedCluster: MessageTypeDefinition + } + } + } + type: { + matcher: { + v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition + ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition + } + } + metadata: { + v3: { + MetadataKey: MessageTypeDefinition + MetadataKind: MessageTypeDefinition + } + } + tracing: { + v3: { + CustomTag: MessageTypeDefinition + } + } + v3: { + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + Empty: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + FloatValue: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + StringValue: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + annotations: { + FieldMigrateAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + ContextParams: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/typed_struct.ts b/packages/grpc-js-xds/src/generated/typed_struct.ts new file mode 100644 index 000000000..e8dca13d5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/typed_struct.ts @@ -0,0 +1,81 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + google: { + protobuf: { + DescriptorProto: MessageTypeDefinition + Duration: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + type: { + v1: { + TypedStruct: MessageTypeDefinition + } + } + } + validate: { + AnyRules: MessageTypeDefinition + BoolRules: MessageTypeDefinition + BytesRules: MessageTypeDefinition + DoubleRules: MessageTypeDefinition + DurationRules: MessageTypeDefinition + EnumRules: MessageTypeDefinition + FieldRules: MessageTypeDefinition + Fixed32Rules: MessageTypeDefinition + Fixed64Rules: MessageTypeDefinition + FloatRules: MessageTypeDefinition + Int32Rules: MessageTypeDefinition + Int64Rules: MessageTypeDefinition + KnownRegex: EnumTypeDefinition + MapRules: MessageTypeDefinition + MessageRules: MessageTypeDefinition + RepeatedRules: MessageTypeDefinition + SFixed32Rules: MessageTypeDefinition + SFixed64Rules: MessageTypeDefinition + SInt32Rules: MessageTypeDefinition + SInt64Rules: MessageTypeDefinition + StringRules: MessageTypeDefinition + TimestampRules: MessageTypeDefinition + UInt32Rules: MessageTypeDefinition + UInt64Rules: MessageTypeDefinition + } + xds: { + type: { + v3: { + TypedStruct: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/FieldMigrateAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldMigrateAnnotation.ts new file mode 100644 index 000000000..4cbe9fdcb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldMigrateAnnotation.ts @@ -0,0 +1,28 @@ +// Original file: deps/xds/udpa/annotations/migrate.proto + + +export interface FieldMigrateAnnotation { + /** + * Rename the field in next version. + */ + 'rename'?: (string); + /** + * Add the field to a named oneof in next version. If this already exists, the + * field will join its siblings under the oneof, otherwise a new oneof will be + * created with the given name. + */ + 'oneof_promotion'?: (string); +} + +export interface FieldMigrateAnnotation__Output { + /** + * Rename the field in next version. + */ + 'rename': (string); + /** + * Add the field to a named oneof in next version. If this already exists, the + * field will join its siblings under the oneof, otherwise a new oneof will be + * created with the given name. + */ + 'oneof_promotion': (string); +} diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts new file mode 100644 index 000000000..9c25fb84e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts @@ -0,0 +1,32 @@ +// Original file: deps/xds/udpa/annotations/security.proto + + +/** + * These annotations indicate metadata for the purpose of understanding the + * security significance of fields. + */ +export interface FieldSecurityAnnotation { + /** + * Field should be set in the presence of untrusted downstreams. + */ + 'configure_for_untrusted_downstream'?: (boolean); + /** + * Field should be set in the presence of untrusted upstreams. + */ + 'configure_for_untrusted_upstream'?: (boolean); +} + +/** + * These annotations indicate metadata for the purpose of understanding the + * security significance of fields. + */ +export interface FieldSecurityAnnotation__Output { + /** + * Field should be set in the presence of untrusted downstreams. + */ + 'configure_for_untrusted_downstream': (boolean); + /** + * Field should be set in the presence of untrusted upstreams. + */ + 'configure_for_untrusted_upstream': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/FileMigrateAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/FileMigrateAnnotation.ts new file mode 100644 index 000000000..95d29245d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/FileMigrateAnnotation.ts @@ -0,0 +1,18 @@ +// Original file: deps/xds/udpa/annotations/migrate.proto + + +export interface FileMigrateAnnotation { + /** + * Move all types in the file to another package, this implies changing proto + * file path. + */ + 'move_to_package'?: (string); +} + +export interface FileMigrateAnnotation__Output { + /** + * Move all types in the file to another package, this implies changing proto + * file path. + */ + 'move_to_package': (string); +} diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/MigrateAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/MigrateAnnotation.ts new file mode 100644 index 000000000..16def9f28 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/MigrateAnnotation.ts @@ -0,0 +1,16 @@ +// Original file: deps/xds/udpa/annotations/migrate.proto + + +export interface MigrateAnnotation { + /** + * Rename the message/enum/enum value in next version. + */ + 'rename'?: (string); +} + +export interface MigrateAnnotation__Output { + /** + * Rename the message/enum/enum value in next version. + */ + 'rename': (string); +} diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts new file mode 100644 index 000000000..d0e181aa5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts @@ -0,0 +1,21 @@ +// Original file: deps/xds/udpa/annotations/status.proto + +export enum PackageVersionStatus { + /** + * Unknown package version status. + */ + UNKNOWN = 0, + /** + * This version of the package is frozen. + */ + FROZEN = 1, + /** + * This version of the package is the active development version. + */ + ACTIVE = 2, + /** + * This version of the package is the candidate for the next major version. It + * is typically machine generated from the active development version. + */ + NEXT_MAJOR_VERSION_CANDIDATE = 3, +} diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts new file mode 100644 index 000000000..f01b45063 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts @@ -0,0 +1,25 @@ +// Original file: deps/xds/udpa/annotations/status.proto + +import type { PackageVersionStatus as _udpa_annotations_PackageVersionStatus } from '../../udpa/annotations/PackageVersionStatus'; + +export interface StatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); + /** + * The entity belongs to a package with the given version status. + */ + 'package_version_status'?: (_udpa_annotations_PackageVersionStatus | keyof typeof _udpa_annotations_PackageVersionStatus); +} + +export interface StatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); + /** + * The entity belongs to a package with the given version status. + */ + 'package_version_status': (keyof typeof _udpa_annotations_PackageVersionStatus); +} diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts new file mode 100644 index 000000000..7a517a06c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts @@ -0,0 +1,20 @@ +// Original file: deps/xds/udpa/annotations/versioning.proto + + +export interface VersioningAnnotation { + /** + * Track the previous message type. E.g. this message might be + * udpa.foo.v3alpha.Foo and it was previously udpa.bar.v2.Bar. This + * information is consumed by UDPA via proto descriptors. + */ + 'previous_message_type'?: (string); +} + +export interface VersioningAnnotation__Output { + /** + * Track the previous message type. E.g. this message might be + * udpa.foo.v3alpha.Foo and it was previously udpa.bar.v2.Bar. This + * information is consumed by UDPA via proto descriptors. + */ + 'previous_message_type': (string); +} diff --git a/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts b/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts new file mode 100644 index 000000000..b080c6348 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts @@ -0,0 +1,77 @@ +// Original file: deps/xds/udpa/type/v1/typed_struct.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../google/protobuf/Struct'; + +/** + * A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that + * describes the type of the serialized message. This is very similar to google.protobuf.Any, + * instead of having protocol buffer binary, this employs google.protobuf.Struct as value. + * + * This message is intended to be embedded inside Any, so it shouldn't be directly referred + * from other UDPA messages. + * + * When packing an opaque extension config, packing the expected type into Any is preferred + * wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor + * is not available, for example if: + * - A control plane sends opaque message that is originally from external source in human readable + * format such as JSON or YAML. + * - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot + * serialize the message in protocol buffer binary format. + * - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension + * uses. This has to be indicated in the DPLB capability negotiation. + * + * When a DPLB receives a TypedStruct in Any, it should: + * - Check if the type_url of the TypedStruct matches the type the extension expects. + * - Convert value to the type described in type_url and perform validation. + * TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link + * protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). + */ +export interface TypedStruct { + /** + * A URL that uniquely identifies the type of the serialize protocol buffer message. + * This has same semantics and format described in google.protobuf.Any: + * https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + */ + 'type_url'?: (string); + /** + * A JSON representation of the above specified type. + */ + 'value'?: (_google_protobuf_Struct | null); +} + +/** + * A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that + * describes the type of the serialized message. This is very similar to google.protobuf.Any, + * instead of having protocol buffer binary, this employs google.protobuf.Struct as value. + * + * This message is intended to be embedded inside Any, so it shouldn't be directly referred + * from other UDPA messages. + * + * When packing an opaque extension config, packing the expected type into Any is preferred + * wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor + * is not available, for example if: + * - A control plane sends opaque message that is originally from external source in human readable + * format such as JSON or YAML. + * - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot + * serialize the message in protocol buffer binary format. + * - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension + * uses. This has to be indicated in the DPLB capability negotiation. + * + * When a DPLB receives a TypedStruct in Any, it should: + * - Check if the type_url of the TypedStruct matches the type the extension expects. + * - Convert value to the type described in type_url and perform validation. + * TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link + * protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). + */ +export interface TypedStruct__Output { + /** + * A URL that uniquely identifies the type of the serialize protocol buffer message. + * This has same semantics and format described in google.protobuf.Any: + * https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + */ + 'type_url': (string); + /** + * A JSON representation of the above specified type. + */ + 'value': (_google_protobuf_Struct__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/validate/AnyRules.ts b/packages/grpc-js-xds/src/generated/validate/AnyRules.ts new file mode 100644 index 000000000..6b16d986c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/AnyRules.ts @@ -0,0 +1,44 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * AnyRules describe constraints applied exclusively to the + * `google.protobuf.Any` well-known type + */ +export interface AnyRules { + /** + * Required specifies that this field must be set + */ + 'required'?: (boolean); + /** + * In specifies that this field's `type_url` must be equal to one of the + * specified values. + */ + 'in'?: (string)[]; + /** + * NotIn specifies that this field's `type_url` must not be equal to any of + * the specified values. + */ + 'not_in'?: (string)[]; +} + +/** + * AnyRules describe constraints applied exclusively to the + * `google.protobuf.Any` well-known type + */ +export interface AnyRules__Output { + /** + * Required specifies that this field must be set + */ + 'required': (boolean); + /** + * In specifies that this field's `type_url` must be equal to one of the + * specified values. + */ + 'in': (string)[]; + /** + * NotIn specifies that this field's `type_url` must not be equal to any of + * the specified values. + */ + 'not_in': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/BoolRules.ts b/packages/grpc-js-xds/src/generated/validate/BoolRules.ts new file mode 100644 index 000000000..3fd2a7a64 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/BoolRules.ts @@ -0,0 +1,22 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * BoolRules describes the constraints applied to `bool` values + */ +export interface BoolRules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (boolean); +} + +/** + * BoolRules describes the constraints applied to `bool` values + */ +export interface BoolRules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/validate/BytesRules.ts b/packages/grpc-js-xds/src/generated/validate/BytesRules.ts new file mode 100644 index 000000000..cba6ca1ba --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/BytesRules.ts @@ -0,0 +1,153 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * BytesRules describe the constraints applied to `bytes` values + */ +export interface BytesRules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (Buffer | Uint8Array | string); + /** + * MinLen specifies that this field must be the specified number of bytes + * at a minimum + */ + 'min_len'?: (number | string | Long); + /** + * MaxLen specifies that this field must be the specified number of bytes + * at a maximum + */ + 'max_len'?: (number | string | Long); + /** + * Pattern specifes that this field must match against the specified + * regular expression (RE2 syntax). The included expression should elide + * any delimiters. + */ + 'pattern'?: (string); + /** + * Prefix specifies that this field must have the specified bytes at the + * beginning of the string. + */ + 'prefix'?: (Buffer | Uint8Array | string); + /** + * Suffix specifies that this field must have the specified bytes at the + * end of the string. + */ + 'suffix'?: (Buffer | Uint8Array | string); + /** + * Contains specifies that this field must have the specified bytes + * anywhere in the string. + */ + 'contains'?: (Buffer | Uint8Array | string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (Buffer | Uint8Array | string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (Buffer | Uint8Array | string)[]; + /** + * Ip specifies that the field must be a valid IP (v4 or v6) address in + * byte format + */ + 'ip'?: (boolean); + /** + * Ipv4 specifies that the field must be a valid IPv4 address in byte + * format + */ + 'ipv4'?: (boolean); + /** + * Ipv6 specifies that the field must be a valid IPv6 address in byte + * format + */ + 'ipv6'?: (boolean); + /** + * Len specifies that this field must be the specified number of bytes + */ + 'len'?: (number | string | Long); + /** + * WellKnown rules provide advanced constraints against common byte + * patterns + */ + 'well_known'?: "ip"|"ipv4"|"ipv6"; +} + +/** + * BytesRules describe the constraints applied to `bytes` values + */ +export interface BytesRules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (Buffer); + /** + * MinLen specifies that this field must be the specified number of bytes + * at a minimum + */ + 'min_len': (string); + /** + * MaxLen specifies that this field must be the specified number of bytes + * at a maximum + */ + 'max_len': (string); + /** + * Pattern specifes that this field must match against the specified + * regular expression (RE2 syntax). The included expression should elide + * any delimiters. + */ + 'pattern': (string); + /** + * Prefix specifies that this field must have the specified bytes at the + * beginning of the string. + */ + 'prefix': (Buffer); + /** + * Suffix specifies that this field must have the specified bytes at the + * end of the string. + */ + 'suffix': (Buffer); + /** + * Contains specifies that this field must have the specified bytes + * anywhere in the string. + */ + 'contains': (Buffer); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (Buffer)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (Buffer)[]; + /** + * Ip specifies that the field must be a valid IP (v4 or v6) address in + * byte format + */ + 'ip'?: (boolean); + /** + * Ipv4 specifies that the field must be a valid IPv4 address in byte + * format + */ + 'ipv4'?: (boolean); + /** + * Ipv6 specifies that the field must be a valid IPv6 address in byte + * format + */ + 'ipv6'?: (boolean); + /** + * Len specifies that this field must be the specified number of bytes + */ + 'len': (string); + /** + * WellKnown rules provide advanced constraints against common byte + * patterns + */ + 'well_known': "ip"|"ipv4"|"ipv6"; +} diff --git a/packages/grpc-js-xds/src/generated/validate/DoubleRules.ts b/packages/grpc-js-xds/src/generated/validate/DoubleRules.ts new file mode 100644 index 000000000..e756c86b9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/DoubleRules.ts @@ -0,0 +1,86 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * DoubleRules describes the constraints applied to `double` values + */ +export interface DoubleRules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number | string); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number | string); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number | string); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number | string); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number | string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number | string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number | string)[]; +} + +/** + * DoubleRules describes the constraints applied to `double` values + */ +export interface DoubleRules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/DurationRules.ts b/packages/grpc-js-xds/src/generated/validate/DurationRules.ts new file mode 100644 index 000000000..879928e20 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/DurationRules.ts @@ -0,0 +1,93 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../google/protobuf/Duration'; + +/** + * DurationRules describe the constraints applied exclusively to the + * `google.protobuf.Duration` well-known type + */ +export interface DurationRules { + /** + * Required specifies that this field must be set + */ + 'required'?: (boolean); + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (_google_protobuf_Duration | null); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (_google_protobuf_Duration | null); + /** + * Lt specifies that this field must be less than the specified value, + * inclusive + */ + 'lte'?: (_google_protobuf_Duration | null); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive + */ + 'gt'?: (_google_protobuf_Duration | null); + /** + * Gte specifies that this field must be greater than the specified value, + * inclusive + */ + 'gte'?: (_google_protobuf_Duration | null); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (_google_protobuf_Duration)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (_google_protobuf_Duration)[]; +} + +/** + * DurationRules describe the constraints applied exclusively to the + * `google.protobuf.Duration` well-known type + */ +export interface DurationRules__Output { + /** + * Required specifies that this field must be set + */ + 'required': (boolean); + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (_google_protobuf_Duration__Output | null); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (_google_protobuf_Duration__Output | null); + /** + * Lt specifies that this field must be less than the specified value, + * inclusive + */ + 'lte': (_google_protobuf_Duration__Output | null); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive + */ + 'gt': (_google_protobuf_Duration__Output | null); + /** + * Gte specifies that this field must be greater than the specified value, + * inclusive + */ + 'gte': (_google_protobuf_Duration__Output | null); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (_google_protobuf_Duration__Output)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (_google_protobuf_Duration__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/EnumRules.ts b/packages/grpc-js-xds/src/generated/validate/EnumRules.ts new file mode 100644 index 000000000..c70eb0b19 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/EnumRules.ts @@ -0,0 +1,52 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * EnumRules describe the constraints applied to enum values + */ +export interface EnumRules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number); + /** + * DefinedOnly specifies that this field must be only one of the defined + * values for this enum, failing on any undefined value. + */ + 'defined_only'?: (boolean); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number)[]; +} + +/** + * EnumRules describe the constraints applied to enum values + */ +export interface EnumRules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (number); + /** + * DefinedOnly specifies that this field must be only one of the defined + * values for this enum, failing on any undefined value. + */ + 'defined_only': (boolean); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/FieldRules.ts b/packages/grpc-js-xds/src/generated/validate/FieldRules.ts new file mode 100644 index 000000000..067125775 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/FieldRules.ts @@ -0,0 +1,103 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { FloatRules as _validate_FloatRules, FloatRules__Output as _validate_FloatRules__Output } from '../validate/FloatRules'; +import type { DoubleRules as _validate_DoubleRules, DoubleRules__Output as _validate_DoubleRules__Output } from '../validate/DoubleRules'; +import type { Int32Rules as _validate_Int32Rules, Int32Rules__Output as _validate_Int32Rules__Output } from '../validate/Int32Rules'; +import type { Int64Rules as _validate_Int64Rules, Int64Rules__Output as _validate_Int64Rules__Output } from '../validate/Int64Rules'; +import type { UInt32Rules as _validate_UInt32Rules, UInt32Rules__Output as _validate_UInt32Rules__Output } from '../validate/UInt32Rules'; +import type { UInt64Rules as _validate_UInt64Rules, UInt64Rules__Output as _validate_UInt64Rules__Output } from '../validate/UInt64Rules'; +import type { SInt32Rules as _validate_SInt32Rules, SInt32Rules__Output as _validate_SInt32Rules__Output } from '../validate/SInt32Rules'; +import type { SInt64Rules as _validate_SInt64Rules, SInt64Rules__Output as _validate_SInt64Rules__Output } from '../validate/SInt64Rules'; +import type { Fixed32Rules as _validate_Fixed32Rules, Fixed32Rules__Output as _validate_Fixed32Rules__Output } from '../validate/Fixed32Rules'; +import type { Fixed64Rules as _validate_Fixed64Rules, Fixed64Rules__Output as _validate_Fixed64Rules__Output } from '../validate/Fixed64Rules'; +import type { SFixed32Rules as _validate_SFixed32Rules, SFixed32Rules__Output as _validate_SFixed32Rules__Output } from '../validate/SFixed32Rules'; +import type { SFixed64Rules as _validate_SFixed64Rules, SFixed64Rules__Output as _validate_SFixed64Rules__Output } from '../validate/SFixed64Rules'; +import type { BoolRules as _validate_BoolRules, BoolRules__Output as _validate_BoolRules__Output } from '../validate/BoolRules'; +import type { StringRules as _validate_StringRules, StringRules__Output as _validate_StringRules__Output } from '../validate/StringRules'; +import type { BytesRules as _validate_BytesRules, BytesRules__Output as _validate_BytesRules__Output } from '../validate/BytesRules'; +import type { EnumRules as _validate_EnumRules, EnumRules__Output as _validate_EnumRules__Output } from '../validate/EnumRules'; +import type { MessageRules as _validate_MessageRules, MessageRules__Output as _validate_MessageRules__Output } from '../validate/MessageRules'; +import type { RepeatedRules as _validate_RepeatedRules, RepeatedRules__Output as _validate_RepeatedRules__Output } from '../validate/RepeatedRules'; +import type { MapRules as _validate_MapRules, MapRules__Output as _validate_MapRules__Output } from '../validate/MapRules'; +import type { AnyRules as _validate_AnyRules, AnyRules__Output as _validate_AnyRules__Output } from '../validate/AnyRules'; +import type { DurationRules as _validate_DurationRules, DurationRules__Output as _validate_DurationRules__Output } from '../validate/DurationRules'; +import type { TimestampRules as _validate_TimestampRules, TimestampRules__Output as _validate_TimestampRules__Output } from '../validate/TimestampRules'; +import type { Long } from '@grpc/proto-loader'; + +/** + * FieldRules encapsulates the rules for each type of field. Depending on the + * field, the correct set should be used to ensure proper validations. + */ +export interface FieldRules { + /** + * Scalar Field Types + */ + 'float'?: (_validate_FloatRules | null); + 'double'?: (_validate_DoubleRules | null); + 'int32'?: (_validate_Int32Rules | null); + 'int64'?: (_validate_Int64Rules | null); + 'uint32'?: (_validate_UInt32Rules | null); + 'uint64'?: (_validate_UInt64Rules | null); + 'sint32'?: (_validate_SInt32Rules | null); + 'sint64'?: (_validate_SInt64Rules | null); + 'fixed32'?: (_validate_Fixed32Rules | null); + 'fixed64'?: (_validate_Fixed64Rules | null); + 'sfixed32'?: (_validate_SFixed32Rules | null); + 'sfixed64'?: (_validate_SFixed64Rules | null); + 'bool'?: (_validate_BoolRules | null); + 'string'?: (_validate_StringRules | null); + 'bytes'?: (_validate_BytesRules | null); + /** + * Complex Field Types + */ + 'enum'?: (_validate_EnumRules | null); + 'message'?: (_validate_MessageRules | null); + 'repeated'?: (_validate_RepeatedRules | null); + 'map'?: (_validate_MapRules | null); + /** + * Well-Known Field Types + */ + 'any'?: (_validate_AnyRules | null); + 'duration'?: (_validate_DurationRules | null); + 'timestamp'?: (_validate_TimestampRules | null); + 'type'?: "float"|"double"|"int32"|"int64"|"uint32"|"uint64"|"sint32"|"sint64"|"fixed32"|"fixed64"|"sfixed32"|"sfixed64"|"bool"|"string"|"bytes"|"enum"|"repeated"|"map"|"any"|"duration"|"timestamp"; +} + +/** + * FieldRules encapsulates the rules for each type of field. Depending on the + * field, the correct set should be used to ensure proper validations. + */ +export interface FieldRules__Output { + /** + * Scalar Field Types + */ + 'float'?: (_validate_FloatRules__Output | null); + 'double'?: (_validate_DoubleRules__Output | null); + 'int32'?: (_validate_Int32Rules__Output | null); + 'int64'?: (_validate_Int64Rules__Output | null); + 'uint32'?: (_validate_UInt32Rules__Output | null); + 'uint64'?: (_validate_UInt64Rules__Output | null); + 'sint32'?: (_validate_SInt32Rules__Output | null); + 'sint64'?: (_validate_SInt64Rules__Output | null); + 'fixed32'?: (_validate_Fixed32Rules__Output | null); + 'fixed64'?: (_validate_Fixed64Rules__Output | null); + 'sfixed32'?: (_validate_SFixed32Rules__Output | null); + 'sfixed64'?: (_validate_SFixed64Rules__Output | null); + 'bool'?: (_validate_BoolRules__Output | null); + 'string'?: (_validate_StringRules__Output | null); + 'bytes'?: (_validate_BytesRules__Output | null); + /** + * Complex Field Types + */ + 'enum'?: (_validate_EnumRules__Output | null); + 'message': (_validate_MessageRules__Output | null); + 'repeated'?: (_validate_RepeatedRules__Output | null); + 'map'?: (_validate_MapRules__Output | null); + /** + * Well-Known Field Types + */ + 'any'?: (_validate_AnyRules__Output | null); + 'duration'?: (_validate_DurationRules__Output | null); + 'timestamp'?: (_validate_TimestampRules__Output | null); + 'type': "float"|"double"|"int32"|"int64"|"uint32"|"uint64"|"sint32"|"sint64"|"fixed32"|"fixed64"|"sfixed32"|"sfixed64"|"bool"|"string"|"bytes"|"enum"|"repeated"|"map"|"any"|"duration"|"timestamp"; +} diff --git a/packages/grpc-js-xds/src/generated/validate/Fixed32Rules.ts b/packages/grpc-js-xds/src/generated/validate/Fixed32Rules.ts new file mode 100644 index 000000000..e88953875 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/Fixed32Rules.ts @@ -0,0 +1,86 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * Fixed32Rules describes the constraints applied to `fixed32` values + */ +export interface Fixed32Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number)[]; +} + +/** + * Fixed32Rules describes the constraints applied to `fixed32` values + */ +export interface Fixed32Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/Fixed64Rules.ts b/packages/grpc-js-xds/src/generated/validate/Fixed64Rules.ts new file mode 100644 index 000000000..98fd1889e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/Fixed64Rules.ts @@ -0,0 +1,87 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * Fixed64Rules describes the constraints applied to `fixed64` values + */ +export interface Fixed64Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number | string | Long); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number | string | Long); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number | string | Long); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number | string | Long); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number | string | Long); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number | string | Long)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number | string | Long)[]; +} + +/** + * Fixed64Rules describes the constraints applied to `fixed64` values + */ +export interface Fixed64Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (string); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (string); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (string); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (string); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/FloatRules.ts b/packages/grpc-js-xds/src/generated/validate/FloatRules.ts new file mode 100644 index 000000000..8d5244c2b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/FloatRules.ts @@ -0,0 +1,86 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * FloatRules describes the constraints applied to `float` values + */ +export interface FloatRules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number | string); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number | string); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number | string); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number | string); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number | string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number | string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number | string)[]; +} + +/** + * FloatRules describes the constraints applied to `float` values + */ +export interface FloatRules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/Int32Rules.ts b/packages/grpc-js-xds/src/generated/validate/Int32Rules.ts new file mode 100644 index 000000000..ea7ed42f3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/Int32Rules.ts @@ -0,0 +1,86 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * Int32Rules describes the constraints applied to `int32` values + */ +export interface Int32Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number)[]; +} + +/** + * Int32Rules describes the constraints applied to `int32` values + */ +export interface Int32Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/Int64Rules.ts b/packages/grpc-js-xds/src/generated/validate/Int64Rules.ts new file mode 100644 index 000000000..3b91b3e09 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/Int64Rules.ts @@ -0,0 +1,87 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * Int64Rules describes the constraints applied to `int64` values + */ +export interface Int64Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number | string | Long); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number | string | Long); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number | string | Long); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number | string | Long); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number | string | Long); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number | string | Long)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number | string | Long)[]; +} + +/** + * Int64Rules describes the constraints applied to `int64` values + */ +export interface Int64Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (string); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (string); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (string); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (string); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/KnownRegex.ts b/packages/grpc-js-xds/src/generated/validate/KnownRegex.ts new file mode 100644 index 000000000..5880b5baf --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/KnownRegex.ts @@ -0,0 +1,16 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +/** + * WellKnownRegex contain some well-known patterns. + */ +export enum KnownRegex { + UNKNOWN = 0, + /** + * HTTP header name as defined by RFC 7230. + */ + HTTP_HEADER_NAME = 1, + /** + * HTTP header value as defined by RFC 7230. + */ + HTTP_HEADER_VALUE = 2, +} diff --git a/packages/grpc-js-xds/src/generated/validate/MapRules.ts b/packages/grpc-js-xds/src/generated/validate/MapRules.ts new file mode 100644 index 000000000..7efe5b390 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/MapRules.ts @@ -0,0 +1,66 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { FieldRules as _validate_FieldRules, FieldRules__Output as _validate_FieldRules__Output } from '../validate/FieldRules'; +import type { Long } from '@grpc/proto-loader'; + +/** + * MapRules describe the constraints applied to `map` values + */ +export interface MapRules { + /** + * MinPairs specifies that this field must have the specified number of + * KVs at a minimum + */ + 'min_pairs'?: (number | string | Long); + /** + * MaxPairs specifies that this field must have the specified number of + * KVs at a maximum + */ + 'max_pairs'?: (number | string | Long); + /** + * NoSparse specifies values in this field cannot be unset. This only + * applies to map's with message value types. + */ + 'no_sparse'?: (boolean); + /** + * Keys specifies the constraints to be applied to each key in the field. + */ + 'keys'?: (_validate_FieldRules | null); + /** + * Values specifies the constraints to be applied to the value of each key + * in the field. Message values will still have their validations evaluated + * unless skip is specified here. + */ + 'values'?: (_validate_FieldRules | null); +} + +/** + * MapRules describe the constraints applied to `map` values + */ +export interface MapRules__Output { + /** + * MinPairs specifies that this field must have the specified number of + * KVs at a minimum + */ + 'min_pairs': (string); + /** + * MaxPairs specifies that this field must have the specified number of + * KVs at a maximum + */ + 'max_pairs': (string); + /** + * NoSparse specifies values in this field cannot be unset. This only + * applies to map's with message value types. + */ + 'no_sparse': (boolean); + /** + * Keys specifies the constraints to be applied to each key in the field. + */ + 'keys': (_validate_FieldRules__Output | null); + /** + * Values specifies the constraints to be applied to the value of each key + * in the field. Message values will still have their validations evaluated + * unless skip is specified here. + */ + 'values': (_validate_FieldRules__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/validate/MessageRules.ts b/packages/grpc-js-xds/src/generated/validate/MessageRules.ts new file mode 100644 index 000000000..d22a85c63 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/MessageRules.ts @@ -0,0 +1,34 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * MessageRules describe the constraints applied to embedded message values. + * For message-type fields, validation is performed recursively. + */ +export interface MessageRules { + /** + * Skip specifies that the validation rules of this field should not be + * evaluated + */ + 'skip'?: (boolean); + /** + * Required specifies that this field must be set + */ + 'required'?: (boolean); +} + +/** + * MessageRules describe the constraints applied to embedded message values. + * For message-type fields, validation is performed recursively. + */ +export interface MessageRules__Output { + /** + * Skip specifies that the validation rules of this field should not be + * evaluated + */ + 'skip': (boolean); + /** + * Required specifies that this field must be set + */ + 'required': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/validate/RepeatedRules.ts b/packages/grpc-js-xds/src/generated/validate/RepeatedRules.ts new file mode 100644 index 000000000..d347b6497 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/RepeatedRules.ts @@ -0,0 +1,60 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { FieldRules as _validate_FieldRules, FieldRules__Output as _validate_FieldRules__Output } from '../validate/FieldRules'; +import type { Long } from '@grpc/proto-loader'; + +/** + * RepeatedRules describe the constraints applied to `repeated` values + */ +export interface RepeatedRules { + /** + * MinItems specifies that this field must have the specified number of + * items at a minimum + */ + 'min_items'?: (number | string | Long); + /** + * MaxItems specifies that this field must have the specified number of + * items at a maximum + */ + 'max_items'?: (number | string | Long); + /** + * Unique specifies that all elements in this field must be unique. This + * contraint is only applicable to scalar and enum types (messages are not + * supported). + */ + 'unique'?: (boolean); + /** + * Items specifies the contraints to be applied to each item in the field. + * Repeated message fields will still execute validation against each item + * unless skip is specified here. + */ + 'items'?: (_validate_FieldRules | null); +} + +/** + * RepeatedRules describe the constraints applied to `repeated` values + */ +export interface RepeatedRules__Output { + /** + * MinItems specifies that this field must have the specified number of + * items at a minimum + */ + 'min_items': (string); + /** + * MaxItems specifies that this field must have the specified number of + * items at a maximum + */ + 'max_items': (string); + /** + * Unique specifies that all elements in this field must be unique. This + * contraint is only applicable to scalar and enum types (messages are not + * supported). + */ + 'unique': (boolean); + /** + * Items specifies the contraints to be applied to each item in the field. + * Repeated message fields will still execute validation against each item + * unless skip is specified here. + */ + 'items': (_validate_FieldRules__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/validate/SFixed32Rules.ts b/packages/grpc-js-xds/src/generated/validate/SFixed32Rules.ts new file mode 100644 index 000000000..892e8f018 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/SFixed32Rules.ts @@ -0,0 +1,86 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * SFixed32Rules describes the constraints applied to `sfixed32` values + */ +export interface SFixed32Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number)[]; +} + +/** + * SFixed32Rules describes the constraints applied to `sfixed32` values + */ +export interface SFixed32Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/SFixed64Rules.ts b/packages/grpc-js-xds/src/generated/validate/SFixed64Rules.ts new file mode 100644 index 000000000..2619e9475 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/SFixed64Rules.ts @@ -0,0 +1,87 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * SFixed64Rules describes the constraints applied to `sfixed64` values + */ +export interface SFixed64Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number | string | Long); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number | string | Long); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number | string | Long); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number | string | Long); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number | string | Long); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number | string | Long)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number | string | Long)[]; +} + +/** + * SFixed64Rules describes the constraints applied to `sfixed64` values + */ +export interface SFixed64Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (string); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (string); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (string); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (string); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/SInt32Rules.ts b/packages/grpc-js-xds/src/generated/validate/SInt32Rules.ts new file mode 100644 index 000000000..744d067fc --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/SInt32Rules.ts @@ -0,0 +1,86 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * SInt32Rules describes the constraints applied to `sint32` values + */ +export interface SInt32Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number)[]; +} + +/** + * SInt32Rules describes the constraints applied to `sint32` values + */ +export interface SInt32Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/SInt64Rules.ts b/packages/grpc-js-xds/src/generated/validate/SInt64Rules.ts new file mode 100644 index 000000000..db02f30c0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/SInt64Rules.ts @@ -0,0 +1,87 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * SInt64Rules describes the constraints applied to `sint64` values + */ +export interface SInt64Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number | string | Long); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number | string | Long); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number | string | Long); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number | string | Long); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number | string | Long); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number | string | Long)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number | string | Long)[]; +} + +/** + * SInt64Rules describes the constraints applied to `sint64` values + */ +export interface SInt64Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (string); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (string); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (string); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (string); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/StringRules.ts b/packages/grpc-js-xds/src/generated/validate/StringRules.ts new file mode 100644 index 000000000..b6bb1e460 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/StringRules.ts @@ -0,0 +1,288 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { KnownRegex as _validate_KnownRegex } from '../validate/KnownRegex'; +import type { Long } from '@grpc/proto-loader'; + +/** + * StringRules describe the constraints applied to `string` values + */ +export interface StringRules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (string); + /** + * MinLen specifies that this field must be the specified number of + * characters (Unicode code points) at a minimum. Note that the number of + * characters may differ from the number of bytes in the string. + */ + 'min_len'?: (number | string | Long); + /** + * MaxLen specifies that this field must be the specified number of + * characters (Unicode code points) at a maximum. Note that the number of + * characters may differ from the number of bytes in the string. + */ + 'max_len'?: (number | string | Long); + /** + * MinBytes specifies that this field must be the specified number of bytes + * at a minimum + */ + 'min_bytes'?: (number | string | Long); + /** + * MaxBytes specifies that this field must be the specified number of bytes + * at a maximum + */ + 'max_bytes'?: (number | string | Long); + /** + * Pattern specifes that this field must match against the specified + * regular expression (RE2 syntax). The included expression should elide + * any delimiters. + */ + 'pattern'?: (string); + /** + * Prefix specifies that this field must have the specified substring at + * the beginning of the string. + */ + 'prefix'?: (string); + /** + * Suffix specifies that this field must have the specified substring at + * the end of the string. + */ + 'suffix'?: (string); + /** + * Contains specifies that this field must have the specified substring + * anywhere in the string. + */ + 'contains'?: (string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (string)[]; + /** + * Email specifies that the field must be a valid email address as + * defined by RFC 5322 + */ + 'email'?: (boolean); + /** + * Hostname specifies that the field must be a valid hostname as + * defined by RFC 1034. This constraint does not support + * internationalized domain names (IDNs). + */ + 'hostname'?: (boolean); + /** + * Ip specifies that the field must be a valid IP (v4 or v6) address. + * Valid IPv6 addresses should not include surrounding square brackets. + */ + 'ip'?: (boolean); + /** + * Ipv4 specifies that the field must be a valid IPv4 address. + */ + 'ipv4'?: (boolean); + /** + * Ipv6 specifies that the field must be a valid IPv6 address. Valid + * IPv6 addresses should not include surrounding square brackets. + */ + 'ipv6'?: (boolean); + /** + * Uri specifies that the field must be a valid, absolute URI as defined + * by RFC 3986 + */ + 'uri'?: (boolean); + /** + * UriRef specifies that the field must be a valid URI as defined by RFC + * 3986 and may be relative or absolute. + */ + 'uri_ref'?: (boolean); + /** + * Len specifies that this field must be the specified number of + * characters (Unicode code points). Note that the number of + * characters may differ from the number of bytes in the string. + */ + 'len'?: (number | string | Long); + /** + * LenBytes specifies that this field must be the specified number of bytes + * at a minimum + */ + 'len_bytes'?: (number | string | Long); + /** + * Address specifies that the field must be either a valid hostname as + * defined by RFC 1034 (which does not support internationalized domain + * names or IDNs), or it can be a valid IP (v4 or v6). + */ + 'address'?: (boolean); + /** + * Uuid specifies that the field must be a valid UUID as defined by + * RFC 4122 + */ + 'uuid'?: (boolean); + /** + * NotContains specifies that this field cannot have the specified substring + * anywhere in the string. + */ + 'not_contains'?: (string); + /** + * WellKnownRegex specifies a common well known pattern defined as a regex. + */ + 'well_known_regex'?: (_validate_KnownRegex | keyof typeof _validate_KnownRegex); + /** + * This applies to regexes HTTP_HEADER_NAME and HTTP_HEADER_VALUE to enable + * strict header validation. + * By default, this is true, and HTTP header validations are RFC-compliant. + * Setting to false will enable a looser validations that only disallows + * \r\n\0 characters, which can be used to bypass header matching rules. + */ + 'strict'?: (boolean); + /** + * WellKnown rules provide advanced constraints against common string + * patterns + */ + 'well_known'?: "email"|"hostname"|"ip"|"ipv4"|"ipv6"|"uri"|"uri_ref"|"address"|"uuid"|"well_known_regex"; +} + +/** + * StringRules describe the constraints applied to `string` values + */ +export interface StringRules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (string); + /** + * MinLen specifies that this field must be the specified number of + * characters (Unicode code points) at a minimum. Note that the number of + * characters may differ from the number of bytes in the string. + */ + 'min_len': (string); + /** + * MaxLen specifies that this field must be the specified number of + * characters (Unicode code points) at a maximum. Note that the number of + * characters may differ from the number of bytes in the string. + */ + 'max_len': (string); + /** + * MinBytes specifies that this field must be the specified number of bytes + * at a minimum + */ + 'min_bytes': (string); + /** + * MaxBytes specifies that this field must be the specified number of bytes + * at a maximum + */ + 'max_bytes': (string); + /** + * Pattern specifes that this field must match against the specified + * regular expression (RE2 syntax). The included expression should elide + * any delimiters. + */ + 'pattern': (string); + /** + * Prefix specifies that this field must have the specified substring at + * the beginning of the string. + */ + 'prefix': (string); + /** + * Suffix specifies that this field must have the specified substring at + * the end of the string. + */ + 'suffix': (string); + /** + * Contains specifies that this field must have the specified substring + * anywhere in the string. + */ + 'contains': (string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (string)[]; + /** + * Email specifies that the field must be a valid email address as + * defined by RFC 5322 + */ + 'email'?: (boolean); + /** + * Hostname specifies that the field must be a valid hostname as + * defined by RFC 1034. This constraint does not support + * internationalized domain names (IDNs). + */ + 'hostname'?: (boolean); + /** + * Ip specifies that the field must be a valid IP (v4 or v6) address. + * Valid IPv6 addresses should not include surrounding square brackets. + */ + 'ip'?: (boolean); + /** + * Ipv4 specifies that the field must be a valid IPv4 address. + */ + 'ipv4'?: (boolean); + /** + * Ipv6 specifies that the field must be a valid IPv6 address. Valid + * IPv6 addresses should not include surrounding square brackets. + */ + 'ipv6'?: (boolean); + /** + * Uri specifies that the field must be a valid, absolute URI as defined + * by RFC 3986 + */ + 'uri'?: (boolean); + /** + * UriRef specifies that the field must be a valid URI as defined by RFC + * 3986 and may be relative or absolute. + */ + 'uri_ref'?: (boolean); + /** + * Len specifies that this field must be the specified number of + * characters (Unicode code points). Note that the number of + * characters may differ from the number of bytes in the string. + */ + 'len': (string); + /** + * LenBytes specifies that this field must be the specified number of bytes + * at a minimum + */ + 'len_bytes': (string); + /** + * Address specifies that the field must be either a valid hostname as + * defined by RFC 1034 (which does not support internationalized domain + * names or IDNs), or it can be a valid IP (v4 or v6). + */ + 'address'?: (boolean); + /** + * Uuid specifies that the field must be a valid UUID as defined by + * RFC 4122 + */ + 'uuid'?: (boolean); + /** + * NotContains specifies that this field cannot have the specified substring + * anywhere in the string. + */ + 'not_contains': (string); + /** + * WellKnownRegex specifies a common well known pattern defined as a regex. + */ + 'well_known_regex'?: (keyof typeof _validate_KnownRegex); + /** + * This applies to regexes HTTP_HEADER_NAME and HTTP_HEADER_VALUE to enable + * strict header validation. + * By default, this is true, and HTTP header validations are RFC-compliant. + * Setting to false will enable a looser validations that only disallows + * \r\n\0 characters, which can be used to bypass header matching rules. + */ + 'strict': (boolean); + /** + * WellKnown rules provide advanced constraints against common string + * patterns + */ + 'well_known': "email"|"hostname"|"ip"|"ipv4"|"ipv6"|"uri"|"uri_ref"|"address"|"uuid"|"well_known_regex"; +} diff --git a/packages/grpc-js-xds/src/generated/validate/TimestampRules.ts b/packages/grpc-js-xds/src/generated/validate/TimestampRules.ts new file mode 100644 index 000000000..69d548126 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/TimestampRules.ts @@ -0,0 +1,106 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../google/protobuf/Timestamp'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../google/protobuf/Duration'; + +/** + * TimestampRules describe the constraints applied exclusively to the + * `google.protobuf.Timestamp` well-known type + */ +export interface TimestampRules { + /** + * Required specifies that this field must be set + */ + 'required'?: (boolean); + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (_google_protobuf_Timestamp | null); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (_google_protobuf_Timestamp | null); + /** + * Lte specifies that this field must be less than the specified value, + * inclusive + */ + 'lte'?: (_google_protobuf_Timestamp | null); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive + */ + 'gt'?: (_google_protobuf_Timestamp | null); + /** + * Gte specifies that this field must be greater than the specified value, + * inclusive + */ + 'gte'?: (_google_protobuf_Timestamp | null); + /** + * LtNow specifies that this must be less than the current time. LtNow + * can only be used with the Within rule. + */ + 'lt_now'?: (boolean); + /** + * GtNow specifies that this must be greater than the current time. GtNow + * can only be used with the Within rule. + */ + 'gt_now'?: (boolean); + /** + * Within specifies that this field must be within this duration of the + * current time. This constraint can be used alone or with the LtNow and + * GtNow rules. + */ + 'within'?: (_google_protobuf_Duration | null); +} + +/** + * TimestampRules describe the constraints applied exclusively to the + * `google.protobuf.Timestamp` well-known type + */ +export interface TimestampRules__Output { + /** + * Required specifies that this field must be set + */ + 'required': (boolean); + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (_google_protobuf_Timestamp__Output | null); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (_google_protobuf_Timestamp__Output | null); + /** + * Lte specifies that this field must be less than the specified value, + * inclusive + */ + 'lte': (_google_protobuf_Timestamp__Output | null); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive + */ + 'gt': (_google_protobuf_Timestamp__Output | null); + /** + * Gte specifies that this field must be greater than the specified value, + * inclusive + */ + 'gte': (_google_protobuf_Timestamp__Output | null); + /** + * LtNow specifies that this must be less than the current time. LtNow + * can only be used with the Within rule. + */ + 'lt_now': (boolean); + /** + * GtNow specifies that this must be greater than the current time. GtNow + * can only be used with the Within rule. + */ + 'gt_now': (boolean); + /** + * Within specifies that this field must be within this duration of the + * current time. This constraint can be used alone or with the LtNow and + * GtNow rules. + */ + 'within': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/validate/UInt32Rules.ts b/packages/grpc-js-xds/src/generated/validate/UInt32Rules.ts new file mode 100644 index 000000000..61873da2f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/UInt32Rules.ts @@ -0,0 +1,86 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + + +/** + * UInt32Rules describes the constraints applied to `uint32` values + */ +export interface UInt32Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number)[]; +} + +/** + * UInt32Rules describes the constraints applied to `uint32` values + */ +export interface UInt32Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (number); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (number); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (number); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (number); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (number); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (number)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/validate/UInt64Rules.ts b/packages/grpc-js-xds/src/generated/validate/UInt64Rules.ts new file mode 100644 index 000000000..2878dfc8d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/validate/UInt64Rules.ts @@ -0,0 +1,87 @@ +// Original file: deps/protoc-gen-validate/validate/validate.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * UInt64Rules describes the constraints applied to `uint64` values + */ +export interface UInt64Rules { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const'?: (number | string | Long); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt'?: (number | string | Long); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte'?: (number | string | Long); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt'?: (number | string | Long); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte'?: (number | string | Long); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in'?: (number | string | Long)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in'?: (number | string | Long)[]; +} + +/** + * UInt64Rules describes the constraints applied to `uint64` values + */ +export interface UInt64Rules__Output { + /** + * Const specifies that this field must be exactly the specified value + */ + 'const': (string); + /** + * Lt specifies that this field must be less than the specified value, + * exclusive + */ + 'lt': (string); + /** + * Lte specifies that this field must be less than or equal to the + * specified value, inclusive + */ + 'lte': (string); + /** + * Gt specifies that this field must be greater than the specified value, + * exclusive. If the value of Gt is larger than a specified Lt or Lte, the + * range is reversed. + */ + 'gt': (string); + /** + * Gte specifies that this field must be greater than or equal to the + * specified value, inclusive. If the value of Gte is larger than a + * specified Lt or Lte, the range is reversed. + */ + 'gte': (string); + /** + * In specifies that this field must be equal to one of the specified + * values + */ + 'in': (string)[]; + /** + * NotIn specifies that this field cannot be equal to one of the specified + * values + */ + 'not_in': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/FieldStatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/FieldStatusAnnotation.ts new file mode 100644 index 000000000..744e13837 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/FieldStatusAnnotation.ts @@ -0,0 +1,16 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + + +export interface FieldStatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); +} + +export interface FieldStatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/FileStatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/FileStatusAnnotation.ts new file mode 100644 index 000000000..cbc3ab3f9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/FileStatusAnnotation.ts @@ -0,0 +1,16 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + + +export interface FileStatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); +} + +export interface FileStatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/MessageStatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/MessageStatusAnnotation.ts new file mode 100644 index 000000000..f403f6506 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/MessageStatusAnnotation.ts @@ -0,0 +1,16 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + + +export interface MessageStatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); +} + +export interface MessageStatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts new file mode 100644 index 000000000..e76e2848f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts @@ -0,0 +1,21 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + +export enum PackageVersionStatus { + /** + * Unknown package version status. + */ + UNKNOWN = 0, + /** + * This version of the package is frozen. + */ + FROZEN = 1, + /** + * This version of the package is the active development version. + */ + ACTIVE = 2, + /** + * This version of the package is the candidate for the next major version. It + * is typically machine generated from the active development version. + */ + NEXT_MAJOR_VERSION_CANDIDATE = 3, +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts new file mode 100644 index 000000000..58efbd8f7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts @@ -0,0 +1,25 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + +import type { PackageVersionStatus as _xds_annotations_v3_PackageVersionStatus } from '../../../xds/annotations/v3/PackageVersionStatus'; + +export interface StatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); + /** + * The entity belongs to a package with the given version status. + */ + 'package_version_status'?: (_xds_annotations_v3_PackageVersionStatus | keyof typeof _xds_annotations_v3_PackageVersionStatus); +} + +export interface StatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); + /** + * The entity belongs to a package with the given version status. + */ + 'package_version_status': (keyof typeof _xds_annotations_v3_PackageVersionStatus); +} diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts new file mode 100644 index 000000000..9505731d7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts @@ -0,0 +1,16 @@ +// Original file: deps/xds/xds/core/v3/authority.proto + + +/** + * xDS authority information. + */ +export interface Authority { + 'name'?: (string); +} + +/** + * xDS authority information. + */ +export interface Authority__Output { + 'name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts new file mode 100644 index 000000000..5d2ce9721 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts @@ -0,0 +1,90 @@ +// Original file: deps/xds/xds/core/v3/collection_entry.proto + +import type { ResourceLocator as _xds_core_v3_ResourceLocator, ResourceLocator__Output as _xds_core_v3_ResourceLocator__Output } from '../../../xds/core/v3/ResourceLocator'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * Inlined resource entry. + */ +export interface _xds_core_v3_CollectionEntry_InlineEntry { + /** + * Optional name to describe the inlined resource. Resource names must + * [a-zA-Z0-9_-\./]+ (TODO(htuch): turn this into a PGV constraint once + * finalized, probably should be a RFC3986 pchar). This name allows + * reference via the #entry directive in ResourceLocator. + */ + 'name'?: (string); + /** + * The resource's logical version. It is illegal to have the same named xDS + * resource name at a given version with different resource payloads. + */ + 'version'?: (string); + /** + * The resource payload, including type URL. + */ + 'resource'?: (_google_protobuf_Any | null); +} + +/** + * Inlined resource entry. + */ +export interface _xds_core_v3_CollectionEntry_InlineEntry__Output { + /** + * Optional name to describe the inlined resource. Resource names must + * [a-zA-Z0-9_-\./]+ (TODO(htuch): turn this into a PGV constraint once + * finalized, probably should be a RFC3986 pchar). This name allows + * reference via the #entry directive in ResourceLocator. + */ + 'name': (string); + /** + * The resource's logical version. It is illegal to have the same named xDS + * resource name at a given version with different resource payloads. + */ + 'version': (string); + /** + * The resource payload, including type URL. + */ + 'resource': (_google_protobuf_Any__Output | null); +} + +/** + * xDS collection resource wrapper. This encapsulates a xDS resource when + * appearing inside a list collection resource. List collection resources are + * regular Resource messages of type: + * + * message Collection { + * repeated CollectionEntry resources = 1; + * } + */ +export interface CollectionEntry { + /** + * A resource locator describing how the member resource is to be located. + */ + 'locator'?: (_xds_core_v3_ResourceLocator | null); + /** + * The resource is inlined in the list collection. + */ + 'inline_entry'?: (_xds_core_v3_CollectionEntry_InlineEntry | null); + 'resource_specifier'?: "locator"|"inline_entry"; +} + +/** + * xDS collection resource wrapper. This encapsulates a xDS resource when + * appearing inside a list collection resource. List collection resources are + * regular Resource messages of type: + * + * message Collection { + * repeated CollectionEntry resources = 1; + * } + */ +export interface CollectionEntry__Output { + /** + * A resource locator describing how the member resource is to be located. + */ + 'locator'?: (_xds_core_v3_ResourceLocator__Output | null); + /** + * The resource is inlined in the list collection. + */ + 'inline_entry'?: (_xds_core_v3_CollectionEntry_InlineEntry__Output | null); + 'resource_specifier': "locator"|"inline_entry"; +} diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts new file mode 100644 index 000000000..19a8a99bb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts @@ -0,0 +1,26 @@ +// Original file: deps/xds/xds/core/v3/context_params.proto + + +/** + * Additional parameters that can be used to select resource variants. These include any + * global context parameters, per-resource type client feature capabilities and per-resource + * type functional attributes. All per-resource type attributes will be `xds.resource.` + * prefixed and some of these are documented below: + * `xds.resource.listening_address`: The value is "IP:port" (e.g. "10.1.1.3:8080") which is + * the listening address of a Listener. Used in a Listener resource query. + */ +export interface ContextParams { + 'params'?: ({[key: string]: string}); +} + +/** + * Additional parameters that can be used to select resource variants. These include any + * global context parameters, per-resource type client feature capabilities and per-resource + * type functional attributes. All per-resource type attributes will be `xds.resource.` + * prefixed and some of these are documented below: + * `xds.resource.listening_address`: The value is "IP:port" (e.g. "10.1.1.3:8080") which is + * the listening address of a Listener. Used in a Listener resource query. + */ +export interface ContextParams__Output { + 'params': ({[key: string]: string}); +} diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts new file mode 100644 index 000000000..bb1f822b8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts @@ -0,0 +1,220 @@ +// Original file: deps/xds/xds/core/v3/resource_locator.proto + +import type { ContextParams as _xds_core_v3_ContextParams, ContextParams__Output as _xds_core_v3_ContextParams__Output } from '../../../xds/core/v3/ContextParams'; +import type { ResourceLocator as _xds_core_v3_ResourceLocator, ResourceLocator__Output as _xds_core_v3_ResourceLocator__Output } from '../../../xds/core/v3/ResourceLocator'; + +/** + * Directives provide information to data-plane load balancers on how xDS + * resource names are to be interpreted and potentially further resolved. For + * example, they may provide alternative resource locators for when primary + * resolution fails. Directives are not part of resource names and do not + * appear in a xDS transport discovery request. + * + * When encoding to URIs, directives take the form: + * + * = + * + * For example, we can have alt=xdstp://foo/bar or entry=some%20thing. Each + * directive value type may have its own string encoding, in the case of + * ResourceLocator there is a recursive URI encoding. + * + * Percent encoding applies to the URI encoding of the directive value. + * Multiple directives are comma-separated, so the reserved characters that + * require percent encoding in a directive value are [',', '#', '[', ']', + * '%']. These are the RFC3986 fragment reserved characters with the addition + * of the xDS scheme specific ','. See + * https://tools.ietf.org/html/rfc3986#page-49 for further details on URI ABNF + * and reserved characters. + */ +export interface _xds_core_v3_ResourceLocator_Directive { + /** + * An alternative resource locator for fallback if the resource is + * unavailable. For example, take the resource locator: + * + * xdstp://foo/some-type/some-route-table#alt=xdstp://bar/some-type/another-route-table + * + * If the data-plane load balancer is unable to reach `foo` to fetch the + * resource, it will fallback to `bar`. Alternative resources do not need + * to have equivalent content, but they should be functional substitutes. + */ + 'alt'?: (_xds_core_v3_ResourceLocator | null); + /** + * List collections support inlining of resources via the entry field in + * Resource. These inlined Resource objects may have an optional name + * field specified. When specified, the entry directive allows + * ResourceLocator to directly reference these inlined resources, e.g. + * xdstp://.../foo#entry=bar. + */ + 'entry'?: (string); + 'directive'?: "alt"|"entry"; +} + +/** + * Directives provide information to data-plane load balancers on how xDS + * resource names are to be interpreted and potentially further resolved. For + * example, they may provide alternative resource locators for when primary + * resolution fails. Directives are not part of resource names and do not + * appear in a xDS transport discovery request. + * + * When encoding to URIs, directives take the form: + * + * = + * + * For example, we can have alt=xdstp://foo/bar or entry=some%20thing. Each + * directive value type may have its own string encoding, in the case of + * ResourceLocator there is a recursive URI encoding. + * + * Percent encoding applies to the URI encoding of the directive value. + * Multiple directives are comma-separated, so the reserved characters that + * require percent encoding in a directive value are [',', '#', '[', ']', + * '%']. These are the RFC3986 fragment reserved characters with the addition + * of the xDS scheme specific ','. See + * https://tools.ietf.org/html/rfc3986#page-49 for further details on URI ABNF + * and reserved characters. + */ +export interface _xds_core_v3_ResourceLocator_Directive__Output { + /** + * An alternative resource locator for fallback if the resource is + * unavailable. For example, take the resource locator: + * + * xdstp://foo/some-type/some-route-table#alt=xdstp://bar/some-type/another-route-table + * + * If the data-plane load balancer is unable to reach `foo` to fetch the + * resource, it will fallback to `bar`. Alternative resources do not need + * to have equivalent content, but they should be functional substitutes. + */ + 'alt'?: (_xds_core_v3_ResourceLocator__Output | null); + /** + * List collections support inlining of resources via the entry field in + * Resource. These inlined Resource objects may have an optional name + * field specified. When specified, the entry directive allows + * ResourceLocator to directly reference these inlined resources, e.g. + * xdstp://.../foo#entry=bar. + */ + 'entry'?: (string); + 'directive': "alt"|"entry"; +} + +// Original file: deps/xds/xds/core/v3/resource_locator.proto + +export enum _xds_core_v3_ResourceLocator_Scheme { + XDSTP = 0, + HTTP = 1, + FILE = 2, +} + +/** + * xDS resource locators identify a xDS resource name and instruct the + * data-plane load balancer on how the resource may be located. + * + * Resource locators have a canonical xdstp:// URI representation: + * + * xdstp://{authority}/{type_url}/{id}?{context_params}{#directive,*} + * + * where context_params take the form of URI query parameters. + * + * Resource locators have a similar canonical http:// URI representation: + * + * http://{authority}/{type_url}/{id}?{context_params}{#directive,*} + * + * Resource locators also have a simplified file:// URI representation: + * + * file:///{id}{#directive,*} + */ +export interface ResourceLocator { + /** + * URI scheme. + */ + 'scheme'?: (_xds_core_v3_ResourceLocator_Scheme | keyof typeof _xds_core_v3_ResourceLocator_Scheme); + /** + * Opaque identifier for the resource. Any '/' will not be escaped during URI + * encoding and will form part of the URI path. This may end + * with ‘*’ for glob collection references. + */ + 'id'?: (string); + /** + * Logical authority for resource (not necessarily transport network address). + * Authorities are opaque in the xDS API, data-plane load balancers will map + * them to concrete network transports such as an xDS management server, e.g. + * via envoy.config.core.v3.ConfigSource. + */ + 'authority'?: (string); + /** + * Fully qualified resource type (as in type URL without types.googleapis.com/ + * prefix). + */ + 'resource_type'?: (string); + /** + * Additional parameters that can be used to select resource variants. + * Matches must be exact, i.e. all context parameters must match exactly and + * there must be no additional context parameters set on the matched + * resource. + */ + 'exact_context'?: (_xds_core_v3_ContextParams | null); + /** + * A list of directives that appear in the xDS resource locator #fragment. + * + * When encoding to URI form, directives are percent encoded with comma + * separation. + */ + 'directives'?: (_xds_core_v3_ResourceLocator_Directive)[]; + 'context_param_specifier'?: "exact_context"; +} + +/** + * xDS resource locators identify a xDS resource name and instruct the + * data-plane load balancer on how the resource may be located. + * + * Resource locators have a canonical xdstp:// URI representation: + * + * xdstp://{authority}/{type_url}/{id}?{context_params}{#directive,*} + * + * where context_params take the form of URI query parameters. + * + * Resource locators have a similar canonical http:// URI representation: + * + * http://{authority}/{type_url}/{id}?{context_params}{#directive,*} + * + * Resource locators also have a simplified file:// URI representation: + * + * file:///{id}{#directive,*} + */ +export interface ResourceLocator__Output { + /** + * URI scheme. + */ + 'scheme': (keyof typeof _xds_core_v3_ResourceLocator_Scheme); + /** + * Opaque identifier for the resource. Any '/' will not be escaped during URI + * encoding and will form part of the URI path. This may end + * with ‘*’ for glob collection references. + */ + 'id': (string); + /** + * Logical authority for resource (not necessarily transport network address). + * Authorities are opaque in the xDS API, data-plane load balancers will map + * them to concrete network transports such as an xDS management server, e.g. + * via envoy.config.core.v3.ConfigSource. + */ + 'authority': (string); + /** + * Fully qualified resource type (as in type URL without types.googleapis.com/ + * prefix). + */ + 'resource_type': (string); + /** + * Additional parameters that can be used to select resource variants. + * Matches must be exact, i.e. all context parameters must match exactly and + * there must be no additional context parameters set on the matched + * resource. + */ + 'exact_context'?: (_xds_core_v3_ContextParams__Output | null); + /** + * A list of directives that appear in the xDS resource locator #fragment. + * + * When encoding to URI form, directives are percent encoded with comma + * separation. + */ + 'directives': (_xds_core_v3_ResourceLocator_Directive__Output)[]; + 'context_param_specifier': "exact_context"; +} diff --git a/packages/grpc-js-xds/src/generated/xds/type/v3/TypedStruct.ts b/packages/grpc-js-xds/src/generated/xds/type/v3/TypedStruct.ts new file mode 100644 index 000000000..a0df831d2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/type/v3/TypedStruct.ts @@ -0,0 +1,77 @@ +// Original file: deps/xds/xds/type/v3/typed_struct.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../google/protobuf/Struct'; + +/** + * A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that + * describes the type of the serialized message. This is very similar to google.protobuf.Any, + * instead of having protocol buffer binary, this employs google.protobuf.Struct as value. + * + * This message is intended to be embedded inside Any, so it shouldn't be directly referred + * from other UDPA messages. + * + * When packing an opaque extension config, packing the expected type into Any is preferred + * wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor + * is not available, for example if: + * - A control plane sends opaque message that is originally from external source in human readable + * format such as JSON or YAML. + * - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot + * serialize the message in protocol buffer binary format. + * - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension + * uses. This has to be indicated in the DPLB capability negotiation. + * + * When a DPLB receives a TypedStruct in Any, it should: + * - Check if the type_url of the TypedStruct matches the type the extension expects. + * - Convert value to the type described in type_url and perform validation. + * TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link + * protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). + */ +export interface TypedStruct { + /** + * A URL that uniquely identifies the type of the serialize protocol buffer message. + * This has same semantics and format described in google.protobuf.Any: + * https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + */ + 'type_url'?: (string); + /** + * A JSON representation of the above specified type. + */ + 'value'?: (_google_protobuf_Struct | null); +} + +/** + * A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that + * describes the type of the serialized message. This is very similar to google.protobuf.Any, + * instead of having protocol buffer binary, this employs google.protobuf.Struct as value. + * + * This message is intended to be embedded inside Any, so it shouldn't be directly referred + * from other UDPA messages. + * + * When packing an opaque extension config, packing the expected type into Any is preferred + * wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor + * is not available, for example if: + * - A control plane sends opaque message that is originally from external source in human readable + * format such as JSON or YAML. + * - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot + * serialize the message in protocol buffer binary format. + * - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension + * uses. This has to be indicated in the DPLB capability negotiation. + * + * When a DPLB receives a TypedStruct in Any, it should: + * - Check if the type_url of the TypedStruct matches the type the extension expects. + * - Convert value to the type described in type_url and perform validation. + * TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link + * protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). + */ +export interface TypedStruct__Output { + /** + * A URL that uniquely identifies the type of the serialize protocol buffer message. + * This has same semantics and format described in google.protobuf.Any: + * https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + */ + 'type_url': (string); + /** + * A JSON representation of the above specified type. + */ + 'value': (_google_protobuf_Struct__Output | null); +} diff --git a/packages/grpc-js-xds/src/google-default-credentials.ts b/packages/grpc-js-xds/src/google-default-credentials.ts new file mode 100644 index 000000000..ec765ee49 --- /dev/null +++ b/packages/grpc-js-xds/src/google-default-credentials.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2021 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. + */ + + +import { GoogleAuth } from 'google-auth-library'; +import { ChannelCredentials, CallCredentials } from '@grpc/grpc-js'; + +export function createGoogleDefaultCredentials(): ChannelCredentials { + const sslCreds = ChannelCredentials.createSsl(); + const googleAuthCreds = CallCredentials.createFromGoogleCredential( + new GoogleAuth() + ); + return sslCreds.compose(googleAuthCreds); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/http-filter.ts b/packages/grpc-js-xds/src/http-filter.ts new file mode 100644 index 000000000..29ce5958f --- /dev/null +++ b/packages/grpc-js-xds/src/http-filter.ts @@ -0,0 +1,246 @@ +/* + * Copyright 2021 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. + */ + +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { experimental, logVerbosity } from '@grpc/grpc-js'; +import { Any__Output } from './generated/google/protobuf/Any'; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import { TypedStruct__Output as TypedStruct__Output } from './generated/xds/type/v3/TypedStruct'; +import { FilterConfig__Output } from './generated/envoy/config/route/v3/FilterConfig'; +import { HttpFilter__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter'; + +const TRACER_NAME = 'http_filter'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPED_STRUCT_UDPA_URL = 'type.googleapis.com/udpa.type.v1.TypedStruct'; +const TYPED_STRUCT_UDPA_NAME = 'udpa.type.v1.TypedStruct'; +const TYPED_STRUCT_XDS_URL = 'type.googleapis.com/xds.type.v3.TypedStruct'; +const TYPED_STRUCT_XDS_NAME = 'xds.type.v3.TypedStruct'; + +const FILTER_CONFIG_URL = 'type.googleapis.com/envoy.config.route.v3.FilterConfig'; +const FILTER_CONFIG_NAME = 'envoy.config.route.v3.FilterConfig'; + +const resourceRoot = loadProtosWithOptionsSync([ + 'udpa/type/v1/typed_struct.proto', + 'xds/type/v3/typed_struct.proto', + 'envoy/config/route/v3/route_components.proto'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/protoc-gen-validate/' + ], + } +); + +export interface HttpFilterConfig { + typeUrl: string; + config: any; +} + +export interface HttpFilterFactoryConstructor { + new(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig): FilterFactory; +} + +export interface HttpFilterRegistryEntry { + parseTopLevelFilterConfig(encodedConfig: Any__Output): HttpFilterConfig | null; + parseOverrideFilterConfig(encodedConfig: Any__Output): HttpFilterConfig | null; + httpFilterConstructor: HttpFilterFactoryConstructor; +} + +const FILTER_REGISTRY = new Map(); + +export function registerHttpFilter(typeName: string, entry: HttpFilterRegistryEntry) { + trace('Registered filter with type URL ' + typeName); + FILTER_REGISTRY.set(typeName, entry); +} + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +function parseAnyMessage(message: Any__Output): MessageType | null { + const typeName = message.type_url.substring(message.type_url.lastIndexOf('/') + 1); + const messageType = resourceRoot.lookup(typeName); + if (messageType) { + const decodedMessage = (messageType as any).decode(message.value); + return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType; + } else { + return null; + } +} + +export function getTopLevelFilterUrl(encodedConfig: Any__Output): string { + let typeUrl: string; + if (encodedConfig.type_url === TYPED_STRUCT_UDPA_URL || encodedConfig.type_url === TYPED_STRUCT_XDS_URL) { + const typedStruct = parseAnyMessage(encodedConfig) + if (typedStruct) { + return typedStruct.type_url; + } else { + throw new Error('Failed to parse TypedStruct'); + } + } else { + return encodedConfig.type_url; + } +} + +export function validateTopLevelFilter(httpFilter: HttpFilter__Output): boolean { + if (!httpFilter.typed_config) { + trace(httpFilter.name + ' validation failed: typed_config unset'); + return false; + } + const encodedConfig = httpFilter.typed_config; + let typeUrl: string; + try { + typeUrl = getTopLevelFilterUrl(encodedConfig); + } catch (e) { + trace(httpFilter.name + ' validation failed with error ' + e.message); + return false; + } + const registryEntry = FILTER_REGISTRY.get(typeUrl); + if (registryEntry) { + const parsedConfig = registryEntry.parseTopLevelFilterConfig(encodedConfig); + if (parsedConfig === null) { + trace(httpFilter.name + ' validation failed: config parsing failed'); + } + return parsedConfig !== null; + } else { + if (httpFilter.is_optional) { + return true; + } else { + trace(httpFilter.name + ' validation failed: filter is not optional and registry does not contain type URL ' + typeUrl); + return false; + } + } +} + +export function validateOverrideFilter(encodedConfig: Any__Output): boolean { + let typeUrl: string; + let realConfig: Any__Output; + let isOptional = false; + if (encodedConfig.type_url === FILTER_CONFIG_URL) { + const filterConfig = parseAnyMessage(encodedConfig); + if (filterConfig) { + isOptional = filterConfig.is_optional; + if (filterConfig.config) { + realConfig = filterConfig.config; + } else { + trace('Override filter validation failed: FilterConfig config field is empty'); + return false; + } + } else { + trace('Override filter validation failed: failed to parse FilterConfig message'); + return false; + } + } else { + realConfig = encodedConfig; + } + if (realConfig.type_url === TYPED_STRUCT_UDPA_URL || realConfig.type_url === TYPED_STRUCT_XDS_URL) { + const typedStruct = parseAnyMessage(encodedConfig); + if (typedStruct) { + typeUrl = typedStruct.type_url; + } else { + trace('Override filter validation failed: failed to parse TypedStruct message'); + return false; + } + } else { + typeUrl = realConfig.type_url; + } + const registryEntry = FILTER_REGISTRY.get(typeUrl); + if (registryEntry) { + const parsedConfig = registryEntry.parseOverrideFilterConfig(encodedConfig); + if (parsedConfig === null) { + trace('Override filter validation failed: config parsing failed. Type URL: ' + typeUrl); + } + return parsedConfig !== null; + } else { + if (isOptional) { + return true; + } else { + trace('Override filter validation failed: filter is not optional and registry does not contain type URL ' + typeUrl); + return false; + } + } +} + +export function parseTopLevelFilterConfig(encodedConfig: Any__Output) { + let typeUrl: string; + try { + typeUrl = getTopLevelFilterUrl(encodedConfig); + } catch (e) { + return null; + } + const registryEntry = FILTER_REGISTRY.get(typeUrl); + if (registryEntry) { + return registryEntry.parseTopLevelFilterConfig(encodedConfig); + } else { + // Filter type URL not found in registry + return null; + } +} + +export function parseOverrideFilterConfig(encodedConfig: Any__Output) { + let typeUrl: string; + let realConfig: Any__Output; + if (encodedConfig.type_url === FILTER_CONFIG_URL) { + const filterConfig = parseAnyMessage(encodedConfig); + if (filterConfig) { + if (filterConfig.config) { + realConfig = filterConfig.config; + } else { + return null; + } + } else { + return null; + } + } else { + realConfig = encodedConfig; + } + if (realConfig.type_url === TYPED_STRUCT_UDPA_URL || realConfig.type_url === TYPED_STRUCT_XDS_URL) { + const typedStruct = parseAnyMessage(encodedConfig); + if (typedStruct) { + typeUrl = typedStruct.type_url; + } else { + return null; + } + } else { + typeUrl = realConfig.type_url; + } + const registryEntry = FILTER_REGISTRY.get(typeUrl); + if (registryEntry) { + return registryEntry.parseOverrideFilterConfig(encodedConfig); + } else { + return null; + } +} + +export function createHttpFilter(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig): FilterFactory | null { + const registryEntry = FILTER_REGISTRY.get(config.typeUrl); + if (registryEntry) { + return new registryEntry.httpFilterConstructor(config, overrideConfig); + } else { + return null; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts new file mode 100644 index 000000000..b02dfbc80 --- /dev/null +++ b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts @@ -0,0 +1,347 @@ +/* + * Copyright 2021 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. + */ + +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { experimental, logVerbosity, Metadata, status } from '@grpc/grpc-js'; +import { Any__Output } from '../generated/google/protobuf/Any'; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import BaseFilter = experimental.BaseFilter; +import CallStream = experimental.CallStream; +import { HttpFilterConfig, registerHttpFilter } from '../http-filter'; +import { HTTPFault__Output } from '../generated/envoy/extensions/filters/http/fault/v3/HTTPFault'; +import { envoyFractionToFraction, Fraction } from '../fraction'; +import { Duration__Output } from '../generated/google/protobuf/Duration'; + +const TRACER_NAME = 'fault_injection'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const resourceRoot = loadProtosWithOptionsSync([ + 'envoy/extensions/filters/http/fault/v3/fault.proto'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build/http-filter + __dirname + '/../../../deps/xds/', + __dirname + '/../../../deps/envoy-api/', + __dirname + '/../../../deps/protoc-gen-validate/' + ], + } +); + +interface FixedDelayConfig { + kind: 'fixed'; + durationMs: number; + percentage: Fraction; +} + +interface HeaderDelayConfig { + kind: 'header'; + percentage: Fraction; +} + +interface GrpcAbortConfig { + kind: 'grpc'; + code: status; + percentage: Fraction; +} + +interface HeaderAbortConfig { + kind: 'header'; + percentage: Fraction; +} + +interface FaultInjectionConfig { + delay: FixedDelayConfig | HeaderDelayConfig | null; + abort: GrpcAbortConfig | HeaderAbortConfig | null; + maxActiveFaults: number; +} + +interface FaultInjectionFilterConfig extends HttpFilterConfig { + typeUrl: 'type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault'; + config: FaultInjectionConfig; +} + +const FAULT_INJECTION_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault'; + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +function parseAnyMessage(message: Any__Output): MessageType | null { + const typeName = message.type_url.substring(message.type_url.lastIndexOf('/') + 1); + const messageType = resourceRoot.lookup(typeName); + if (messageType) { + const decodedMessage = (messageType as any).decode(message.value); + return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType; + } else { + return null; + } +} + +function durationToMs(duration: Duration__Output): number { + return Number.parseInt(duration.seconds) * 1000 + duration.nanos / 1_000_000; +} + +function httpCodeToGrpcStatus(code: number): status { + switch (code) { + case 400: return status.INTERNAL; + case 401: return status.UNAUTHENTICATED; + case 403: return status.PERMISSION_DENIED; + case 404: return status.UNIMPLEMENTED; + case 429: return status.UNAVAILABLE; + case 502: return status.UNAVAILABLE; + case 503: return status.UNAVAILABLE; + case 504: return status.UNAVAILABLE; + default: return status.UNKNOWN; + } +} + +function parseHTTPFaultConfig(encodedConfig: Any__Output): FaultInjectionFilterConfig | null { + if (encodedConfig.type_url !== FAULT_INJECTION_FILTER_URL) { + trace('Config parsing failed: unexpected type URL: ' + encodedConfig.type_url); + return null; + } + const parsedMessage = parseAnyMessage(encodedConfig); + if (parsedMessage === null) { + trace('Config parsing failed: failed to parse HTTPFault message'); + return null; + } + trace('Parsing HTTPFault message ' + JSON.stringify(parsedMessage, undefined, 2)); + const result: FaultInjectionConfig = { + delay: null, + abort: null, + maxActiveFaults: Infinity + }; + // Parse delay field + if (parsedMessage.delay !== null) { + if (parsedMessage.delay.percentage === null) { + trace('Config parsing failed: delay.percentage unset'); + return null; + } + const percentage = envoyFractionToFraction(parsedMessage.delay.percentage); + switch (parsedMessage.delay.fault_delay_secifier /* sic */) { + case 'fixed_delay': + result.delay = { + kind: 'fixed', + durationMs: durationToMs(parsedMessage.delay.fixed_delay!), + percentage: percentage + }; + break; + case 'header_delay': + result.delay = { + kind: 'header', + percentage: percentage + }; + break; + default: + trace('Config parsing failed: delay.fault_delay_secifier has unexpected value ' + parsedMessage.delay.fault_delay_secifier); + // Should not be possible + return null; + } + } + // Parse abort field + if (parsedMessage.abort !== null) { + if (parsedMessage.abort.percentage === null) { + trace('Config parsing failed: abort.percentage unset'); + return null; + } + const percentage = envoyFractionToFraction(parsedMessage.abort.percentage); + switch (parsedMessage.abort.error_type) { + case 'http_status': + result.abort = { + kind: 'grpc', + code: httpCodeToGrpcStatus(parsedMessage.abort.http_status!), + percentage: percentage + }; + break; + case 'grpc_status': + result.abort = { + kind: 'grpc', + code: parsedMessage.abort.grpc_status!, + percentage: percentage + } + break; + case 'header_abort': + result.abort = { + kind: 'header', + percentage: percentage + }; + break; + default: + trace('Config parsing failed: abort.error_type has unexpected value ' + parsedMessage.abort.error_type); + // Should not be possible + return null; + } + } + // Parse max_active_faults field + if (parsedMessage.max_active_faults !== null) { + result.maxActiveFaults = parsedMessage.max_active_faults.value; + } + return { + typeUrl: FAULT_INJECTION_FILTER_URL, + config: result + }; +} + +function asyncTimeout(timeMs: number): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, timeMs); + }); +} + +/** + * Returns true with probability numerator/denominator. + * @param numerator + * @param denominator + */ +function rollRandomPercentage(numerator: number, denominator: number): boolean { + return Math.random() * denominator < numerator; +} + +const DELAY_DURATION_HEADER_KEY = 'x-envoy-fault-delay-request'; +const DELAY_PERCENTAGE_HEADER_KEY = 'x-envoy-fault-delay-request-percentage'; +const ABORT_GRPC_HEADER_KEY = 'x-envoy-fault-abort-grpc-request'; +const ABORT_HTTP_HEADER_KEY = 'x-envoy-fault-abort-request'; +const ABORT_PERCENTAGE_HEADER_KEY = 'x-envoy-fault-abort-request-percentage'; + +const NUMBER_REGEX = /\d+/; + +let totalActiveFaults = 0; + +class FaultInjectionFilter extends BaseFilter implements Filter { + constructor(private config: FaultInjectionConfig) { + super(); + } + + async sendMetadata(metadataPromise: Promise): Promise { + const metadata = await metadataPromise; + // Handle delay + if (totalActiveFaults < this.config.maxActiveFaults && this.config.delay) { + let duration = 0; + let numerator = this.config.delay.percentage.numerator; + const denominator = this.config.delay.percentage.denominator; + if (this.config.delay.kind === 'fixed') { + duration = this.config.delay.durationMs; + } else { + const durationHeader = metadata.get(DELAY_DURATION_HEADER_KEY); + for (const value of durationHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + duration = Number.parseInt(value); + break; + } + } + const percentageHeader = metadata.get(DELAY_PERCENTAGE_HEADER_KEY); + for (const value of percentageHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + numerator = Math.min(numerator, Number.parseInt(value)); + break; + } + } + } + if (rollRandomPercentage(numerator, denominator)) { + totalActiveFaults++; + await asyncTimeout(duration); + totalActiveFaults--; + } + } + // Handle abort + if (totalActiveFaults < this.config.maxActiveFaults && this.config.abort) { + let abortStatus: status | null = null; + let numerator = this.config.abort.percentage.numerator; + const denominator = this.config.abort.percentage.denominator; + if (this.config.abort.kind === 'grpc') { + abortStatus = this.config.abort.code; + } else { + const grpcStatusHeader = metadata.get(ABORT_GRPC_HEADER_KEY); + for (const value of grpcStatusHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + abortStatus = Number.parseInt(value); + break; + } + } + /* Fall back to looking for HTTP status header if the gRPC status + * header is not present. */ + if (abortStatus === null) { + const httpStatusHeader = metadata.get(ABORT_HTTP_HEADER_KEY); + for (const value of httpStatusHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + abortStatus = httpCodeToGrpcStatus(Number.parseInt(value)); + break; + } + } + } + const percentageHeader = metadata.get(ABORT_PERCENTAGE_HEADER_KEY); + for (const value of percentageHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + numerator = Math.min(numerator, Number.parseInt(value)); + break; + } + } + } + if (abortStatus !== null && rollRandomPercentage(numerator, denominator)) { + return Promise.reject({code: abortStatus, details: 'Fault injected', metadata: new Metadata()}); + } + } + return metadata; + } +} + +class FaultInjectionFilterFactory implements FilterFactory { + private config: FaultInjectionConfig; + constructor(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig) { + if (overrideConfig?.typeUrl === FAULT_INJECTION_FILTER_URL) { + this.config = overrideConfig.config; + } else { + this.config = config.config; + } + } + + createFilter(): FaultInjectionFilter { + return new FaultInjectionFilter(this.config); + } +} + +export function setup() { + registerHttpFilter(FAULT_INJECTION_FILTER_URL, { + parseTopLevelFilterConfig: parseHTTPFaultConfig, + parseOverrideFilterConfig: parseHTTPFaultConfig, + httpFilterConstructor: FaultInjectionFilterFactory + }); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/http-filter/router-filter.ts b/packages/grpc-js-xds/src/http-filter/router-filter.ts new file mode 100644 index 000000000..172a08740 --- /dev/null +++ b/packages/grpc-js-xds/src/http-filter/router-filter.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2021 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. + */ + +import { experimental } from '@grpc/grpc-js'; +import { Any__Output } from '../generated/google/protobuf/Any'; +import { HttpFilterConfig, registerHttpFilter } from '../http-filter'; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import BaseFilter = experimental.BaseFilter; + +class RouterFilter extends BaseFilter implements Filter {} + +class RouterFilterFactory implements FilterFactory { + constructor(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig) {} + + createFilter(): RouterFilter { + return new RouterFilter(); + } +} + +const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; + +function parseConfig(encodedConfig: Any__Output): HttpFilterConfig | null { + return { + typeUrl: ROUTER_FILTER_URL, + config: null + }; +} + +export function setup() { + registerHttpFilter(ROUTER_FILTER_URL, { + parseTopLevelFilterConfig: parseConfig, + parseOverrideFilterConfig: parseConfig, + httpFilterConstructor: RouterFilterFactory + }); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts new file mode 100644 index 000000000..ca1cf72ef --- /dev/null +++ b/packages/grpc-js-xds/src/index.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2020 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. + * + */ + +import * as resolver_xds from './resolver-xds'; +import * as load_balancer_cds from './load-balancer-cds'; +import * as load_balancer_eds from './load-balancer-eds'; +import * as load_balancer_lrs from './load-balancer-lrs'; +import * as load_balancer_priority from './load-balancer-priority'; +import * as load_balancer_weighted_target from './load-balancer-weighted-target'; +import * as load_balancer_xds_cluster_manager from './load-balancer-xds-cluster-manager'; +import * as router_filter from './http-filter/router-filter'; +import * as fault_injection_filter from './http-filter/fault-injection-filter'; +import * as csds from './csds'; + +/** + * Register the "xds:" name scheme with the @grpc/grpc-js library. + */ +export function register() { + resolver_xds.setup(); + load_balancer_cds.setup(); + load_balancer_eds.setup(); + load_balancer_lrs.setup(); + load_balancer_priority.setup(); + load_balancer_weighted_target.setup(); + load_balancer_xds_cluster_manager.setup(); + router_filter.setup(); + fault_injection_filter.setup(); + csds.setup(); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts new file mode 100644 index 000000000..243a1e967 --- /dev/null +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -0,0 +1,244 @@ +/* + * Copyright 2020 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. + * + */ + +import { connectivityState, status, Metadata, logVerbosity, experimental } from '@grpc/grpc-js'; +import { getSingletonXdsClient, XdsClient } from './xds-client'; +import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; +import SubchannelAddress = experimental.SubchannelAddress; +import UnavailablePicker = experimental.UnavailablePicker; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import LoadBalancer = experimental.LoadBalancer; +import ChannelControlHelper = experimental.ChannelControlHelper; +import registerLoadBalancerType = experimental.registerLoadBalancerType; +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; +import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; +import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; +import { EdsLoadBalancingConfig } from './load-balancer-eds'; +import { Watcher } from './xds-stream-state/xds-stream-state'; +import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection'; +import { Duration__Output } from './generated/google/protobuf/Duration'; +import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; + +const TRACER_NAME = 'cds_balancer'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'cds'; + +export class CdsLoadBalancingConfig implements LoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + + toJsonObject(): object { + return { + [TYPE_NAME]: { + cluster: this.cluster + } + } + } + + constructor(private cluster: string) {} + + getCluster() { + return this.cluster; + } + + static createFromJson(obj: any): CdsLoadBalancingConfig { + if ('cluster' in obj) { + return new CdsLoadBalancingConfig(obj.cluster); + } else { + throw new Error('Missing "cluster" in cds load balancing config'); + } + } +} + +function durationToMs(duration: Duration__Output): number { + return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0; +} + +function translateOutlierDetectionConfig(outlierDetection: OutlierDetection__Output | null): OutlierDetectionLoadBalancingConfig | undefined { + if (!EXPERIMENTAL_OUTLIER_DETECTION) { + return undefined; + } + if (!outlierDetection) { + /* No-op outlier detection config, with all fields unset. */ + return new OutlierDetectionLoadBalancingConfig(null, null, null, null, null, null, []); + } + let successRateConfig: Partial | null = null; + /* Success rate ejection is enabled by default, so we only disable it if + * enforcing_success_rate is set and it has the value 0 */ + if (!outlierDetection.enforcing_success_rate || outlierDetection.enforcing_success_rate.value > 0) { + successRateConfig = { + enforcement_percentage: outlierDetection.enforcing_success_rate?.value, + minimum_hosts: outlierDetection.success_rate_minimum_hosts?.value, + request_volume: outlierDetection.success_rate_request_volume?.value, + stdev_factor: outlierDetection.success_rate_stdev_factor?.value + }; + } + let failurePercentageConfig: Partial | null = null; + /* Failure percentage ejection is disabled by default, so we only enable it + * if enforcing_failure_percentage is set and it has a value greater than 0 */ + if (outlierDetection.enforcing_failure_percentage && outlierDetection.enforcing_failure_percentage.value > 0) { + failurePercentageConfig = { + enforcement_percentage: outlierDetection.enforcing_failure_percentage.value, + minimum_hosts: outlierDetection.failure_percentage_minimum_hosts?.value, + request_volume: outlierDetection.failure_percentage_request_volume?.value, + threshold: outlierDetection.failure_percentage_threshold?.value + } + } + return new OutlierDetectionLoadBalancingConfig( + outlierDetection.interval ? durationToMs(outlierDetection.interval) : null, + outlierDetection.base_ejection_time ? durationToMs(outlierDetection.base_ejection_time) : null, + outlierDetection.max_ejection_time ? durationToMs(outlierDetection.max_ejection_time) : null, + outlierDetection.max_ejection_percent?.value ?? null, + successRateConfig, + failurePercentageConfig, + [] + ); +} + +export class CdsLoadBalancer implements LoadBalancer { + private childBalancer: ChildLoadBalancerHandler; + private watcher: Watcher; + + private isWatcherActive = false; + + private latestCdsUpdate: Cluster__Output | null = null; + + private latestConfig: CdsLoadBalancingConfig | null = null; + private latestAttributes: { [key: string]: unknown } = {}; + private xdsClient: XdsClient | null = null; + + constructor(private readonly channelControlHelper: ChannelControlHelper) { + this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper); + this.watcher = { + onValidUpdate: (update) => { + this.latestCdsUpdate = update; + let maxConcurrentRequests: number | undefined = undefined; + for (const threshold of update.circuit_breakers?.thresholds ?? []) { + if (threshold.priority === 'DEFAULT') { + maxConcurrentRequests = threshold.max_requests?.value; + } + } + /* the lrs_server.self field indicates that the same server should be + * used for load reporting as for other xDS operations. Setting + * lrsLoadReportingServerName to the empty string sets that behavior. + * Otherwise, if the field is omitted, load reporting is disabled. */ + const edsConfig: EdsLoadBalancingConfig = new EdsLoadBalancingConfig( + /* cluster= */ update.name, + /* localityPickingPolicy= */ [], + /* endpointPickingPolicy= */ [], + /* edsServiceName= */ update.eds_cluster_config!.service_name === '' ? undefined : update.eds_cluster_config!.service_name, + /* lrsLoadReportingServerName= */update.lrs_server?.self ? '' : undefined, + /* maxConcurrentRequests= */ maxConcurrentRequests, + /* outlierDetection= */ translateOutlierDetectionConfig(update.outlier_detection) + ); + trace('Child update EDS config: ' + JSON.stringify(edsConfig)); + this.childBalancer.updateAddressList( + [], + edsConfig, + this.latestAttributes + ); + }, + onResourceDoesNotExist: () => { + this.isWatcherActive = false; + this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: 'CDS resource does not exist', metadata: new Metadata()})); + this.childBalancer.destroy(); + }, + onTransientError: (statusObj) => { + if (this.latestCdsUpdate === null) { + channelControlHelper.updateState( + connectivityState.TRANSIENT_FAILURE, + new UnavailablePicker({ + code: status.UNAVAILABLE, + details: `xDS request failed with error ${statusObj.details}`, + metadata: new Metadata(), + }) + ); + } + }, + }; + } + + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void { + if (!(lbConfig instanceof CdsLoadBalancingConfig)) { + trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2)); + return; + } + trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); + this.latestAttributes = attributes; + this.xdsClient = attributes.xdsClient as XdsClient; + + /* If the cluster is changing, disable the old watcher before adding the new + * one */ + if ( + this.isWatcherActive && + this.latestConfig?.getCluster() !== lbConfig.getCluster() + ) { + trace('Removing old cluster watcher for cluster name ' + this.latestConfig!.getCluster()); + this.xdsClient.removeClusterWatcher( + this.latestConfig!.getCluster(), + this.watcher + ); + /* Setting isWatcherActive to false here lets us have one code path for + * calling addClusterWatcher */ + this.isWatcherActive = false; + /* If we have a new name, the latestCdsUpdate does not correspond to + * the new config, so it is no longer valid */ + this.latestCdsUpdate = null; + } + + this.latestConfig = lbConfig; + + if (!this.isWatcherActive) { + trace('Adding new cluster watcher for cluster name ' + lbConfig.getCluster()); + this.xdsClient.addClusterWatcher(lbConfig.getCluster(), this.watcher); + this.isWatcherActive = true; + } + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + trace('Destroying load balancer with cluster name ' + this.latestConfig?.getCluster()); + this.childBalancer.destroy(); + if (this.isWatcherActive) { + this.xdsClient?.removeClusterWatcher( + this.latestConfig!.getCluster(), + this.watcher + ); + } + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, CdsLoadBalancer, CdsLoadBalancingConfig); +} diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts new file mode 100644 index 000000000..03a4078ac --- /dev/null +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -0,0 +1,549 @@ +/* + * Copyright 2020 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. + * + */ + +import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, StatusObject } from '@grpc/grpc-js'; +import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from './xds-client'; +import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; +import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; +import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from './load-balancer-priority'; +import LoadBalancer = experimental.LoadBalancer; +import ChannelControlHelper = experimental.ChannelControlHelper; +import registerLoadBalancerType = experimental.registerLoadBalancerType; +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import SubchannelAddress = experimental.SubchannelAddress; +import subchannelAddressToString = experimental.subchannelAddressToString; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import UnavailablePicker = experimental.UnavailablePicker; +import Picker = experimental.Picker; +import PickResultType = experimental.PickResultType; +import { validateLoadBalancingConfig } from '@grpc/grpc-js/build/src/experimental'; +import { WeightedTarget, WeightedTargetLoadBalancingConfig } from './load-balancer-weighted-target'; +import { LrsLoadBalancingConfig } from './load-balancer-lrs'; +import { Watcher } from './xds-stream-state/xds-stream-state'; +import Filter = experimental.Filter; +import BaseFilter = experimental.BaseFilter; +import FilterFactory = experimental.FilterFactory; +import CallStream = experimental.CallStream; +import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; +import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; + +const TRACER_NAME = 'eds_balancer'; + +function trace(text: string): void { + experimental.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'eds'; + +function localityToName(locality: Locality__Output) { + return `{region=${locality.region},zone=${locality.zone},sub_zone=${locality.sub_zone}}`; +} + +const DEFAULT_MAX_CONCURRENT_REQUESTS = 1024; + +export class EdsLoadBalancingConfig implements LoadBalancingConfig { + private maxConcurrentRequests: number; + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + const jsonObj: {[key: string]: any} = { + cluster: this.cluster, + locality_picking_policy: this.localityPickingPolicy.map(policy => policy.toJsonObject()), + endpoint_picking_policy: this.endpointPickingPolicy.map(policy => policy.toJsonObject()), + max_concurrent_requests: this.maxConcurrentRequests + }; + if (this.edsServiceName !== undefined) { + jsonObj.eds_service_name = this.edsServiceName; + } + if (this.lrsLoadReportingServerName !== undefined) { + jsonObj.lrs_load_reporting_server_name = this.lrsLoadReportingServerName; + } + if (this.outlierDetection !== undefined) { + jsonObj.outlier_detection = this.outlierDetection.toJsonObject(); + } + return { + [TYPE_NAME]: jsonObj + }; + } + + constructor(private cluster: string, private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServerName?: string, maxConcurrentRequests?: number, private outlierDetection?: OutlierDetectionLoadBalancingConfig) { + this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; + } + + getCluster() { + return this.cluster; + } + + getLocalityPickingPolicy() { + return this.localityPickingPolicy; + } + + getEndpointPickingPolicy() { + return this.endpointPickingPolicy; + } + + getEdsServiceName() { + return this.edsServiceName; + } + + getLrsLoadReportingServerName() { + return this.lrsLoadReportingServerName; + } + + getMaxConcurrentRequests() { + return this.maxConcurrentRequests; + } + + getOutlierDetection() { + return this.outlierDetection; + } + + static createFromJson(obj: any): EdsLoadBalancingConfig { + if (!('cluster' in obj && typeof obj.cluster === 'string')) { + throw new Error('eds config must have a string field cluster'); + } + if (!('locality_picking_policy' in obj && Array.isArray(obj.locality_picking_policy))) { + throw new Error('eds config must have a locality_picking_policy array'); + } + if (!('endpoint_picking_policy' in obj && Array.isArray(obj.endpoint_picking_policy))) { + throw new Error('eds config must have an endpoint_picking_policy array'); + } + if ('eds_service_name' in obj && !(obj.eds_service_name === undefined || typeof obj.eds_service_name === 'string')) { + throw new Error('eds config eds_service_name field must be a string if provided'); + } + if ('lrs_load_reporting_server_name' in obj && (!obj.lrs_load_reporting_server_name === undefined || typeof obj.lrs_load_reporting_server_name === 'string')) { + throw new Error('eds config lrs_load_reporting_server_name must be a string if provided'); + } + if ('max_concurrent_requests' in obj && (!obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { + throw new Error('eds config max_concurrent_requests must be a number if provided'); + } + let validatedOutlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined = undefined; + if (EXPERIMENTAL_OUTLIER_DETECTION) { + if ('outlier_detection' in obj) { + const outlierDetectionConfig = validateLoadBalancingConfig(obj.outlier_detection); + if (!(outlierDetectionConfig instanceof OutlierDetectionLoadBalancingConfig)) { + throw new Error('eds config outlier_detection must be a valid outlier detection config if provided'); + } + validatedOutlierDetectionConfig = outlierDetectionConfig; + } + } + return new EdsLoadBalancingConfig(obj.cluster, obj.locality_picking_policy.map(validateLoadBalancingConfig), obj.endpoint_picking_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server_name, obj.max_concurrent_requests, validatedOutlierDetectionConfig); + } +} + +/** + * This class load balances over a cluster by making an EDS request and then + * transforming the result into a configuration for another load balancing + * policy. + */ +export class EdsLoadBalancer implements LoadBalancer { + /** + * The child load balancer that will handle balancing the results of the EDS + * requests. + */ + private childBalancer: ChildLoadBalancerHandler; + private edsServiceName: string | null = null; + private watcher: Watcher; + /** + * Indicates whether the watcher has already been passed to the xdsClient + * and is getting updates. + */ + private isWatcherActive = false; + + private lastestConfig: EdsLoadBalancingConfig | null = null; + private latestAttributes: { [key: string]: unknown } = {}; + private xdsClient: XdsClient | null = null; + private latestEdsUpdate: ClusterLoadAssignment__Output | null = null; + + /** + * The priority of each locality the last time we got an update. + */ + private localityPriorities: Map = new Map(); + /** + * The name we assigned to each priority number the last time we got an + * update. + */ + private priorityNames: string[] = []; + + private nextPriorityChildNumber = 0; + + private clusterDropStats: XdsClusterDropStats | null = null; + + private concurrentRequests: number = 0; + + constructor(private readonly channelControlHelper: ChannelControlHelper) { + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.channelControlHelper, { + updateState: (connectivityState, originalPicker) => { + if (this.latestEdsUpdate === null) { + return; + } + const edsPicker: Picker = { + pick: (pickArgs) => { + const dropCategory = this.checkForDrop(); + /* If we drop the call, it ends with an UNAVAILABLE status. + * Otherwise, delegate picking the subchannel to the child + * balancer. */ + if (dropCategory === null) { + const originalPick = originalPicker.pick(pickArgs); + return { + pickResultType: originalPick.pickResultType, + status: originalPick.status, + subchannel: originalPick.subchannel, + onCallStarted: () => { + originalPick.onCallStarted?.(); + this.concurrentRequests += 1; + }, + onCallEnded: status => { + originalPick.onCallEnded?.(status); + this.concurrentRequests -= 1; + } + }; + } else { + let details: string; + if (dropCategory === true) { + details = 'Call dropped by load balancing policy.'; + this.clusterDropStats?.addUncategorizedCallDropped(); + } else { + details = `Call dropped by load balancing policy. Category: ${dropCategory}`; + this.clusterDropStats?.addCallDropped(dropCategory); + } + return { + pickResultType: PickResultType.DROP, + status: { + code: Status.UNAVAILABLE, + details: details, + metadata: new Metadata(), + }, + subchannel: null, + onCallEnded: null, + onCallStarted: null + }; + } + }, + }; + this.channelControlHelper.updateState(connectivityState, edsPicker); + }, + })); + this.watcher = { + onValidUpdate: (update) => { + trace('Received EDS update for ' + this.edsServiceName + ': ' + JSON.stringify(update, undefined, 2)); + this.latestEdsUpdate = update; + this.updateChild(); + }, + onResourceDoesNotExist: () => { + this.isWatcherActive = false; + this.channelControlHelper.updateState(ConnectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: Status.UNAVAILABLE, details: 'EDS resource does not exist', metadata: new Metadata()})); + this.childBalancer.destroy(); + }, + onTransientError: (status) => { + if (this.latestEdsUpdate === null) { + channelControlHelper.updateState( + ConnectivityState.TRANSIENT_FAILURE, + new UnavailablePicker({ + code: Status.UNAVAILABLE, + details: `xDS request failed with error ${status.details}`, + metadata: new Metadata(), + }) + ); + } + }, + }; + } + + /** + * Check whether a single call should be dropped according to the current + * policy, based on randomly chosen numbers. Returns the drop category if + * the call should be dropped, and null otherwise. true is a valid + * output, as a sentinel value indicating a drop with no category. + */ + private checkForDrop(): string | true | null { + if (this.lastestConfig && this.concurrentRequests >= this.lastestConfig.getMaxConcurrentRequests()) { + return true; + } + if (!this.latestEdsUpdate?.policy) { + return null; + } + /* The drop_overloads policy is a list of pairs of category names and + * probabilities. For each one, if the random number is within that + * probability range, we drop the call citing that category. Otherwise, the + * call proceeds as usual. */ + for (const dropOverload of this.latestEdsUpdate.policy.drop_overloads) { + if (!dropOverload.drop_percentage) { + continue; + } + let randNum: number; + switch (dropOverload.drop_percentage.denominator) { + case 'HUNDRED': + randNum = Math.random() * 100; + break; + case 'TEN_THOUSAND': + randNum = Math.random() * 10_000; + break; + case 'MILLION': + randNum = Math.random() * 1_000_000; + break; + default: + continue; + } + if (randNum < dropOverload.drop_percentage.numerator) { + return dropOverload.category; + } + } + return null; + } + + /** + * Should be called when this balancer gets a new config and when the + * XdsClient returns a new ClusterLoadAssignment. + */ + private updateChild() { + if (!(this.lastestConfig && this.latestEdsUpdate)) { + return; + } + /** + * Maps each priority number to the list of localities with that priority, + * and the list of addresses associated with each locality. + */ + const priorityList: { + locality: Locality__Output; + weight: number; + addresses: SubchannelAddress[]; + }[][] = []; + /** + * New replacement for this.localityPriorities, mapping locality names to + * priority values. The replacement occurrs at the end of this method. + */ + const newLocalityPriorities: Map = new Map< + string, + number + >(); + /* We are given a list of localities, each of which has a priority. This + * loop consolidates localities into buckets by priority, while also + * simplifying the data structure to make the later steps simpler */ + for (const endpoint of this.latestEdsUpdate.endpoints) { + if (!endpoint.load_balancing_weight) { + continue; + } + const addresses: SubchannelAddress[] = endpoint.lb_endpoints.filter(lbEndpoint => lbEndpoint.health_status === 'UNKNOWN' || lbEndpoint.health_status === 'HEALTHY').map( + (lbEndpoint) => { + /* The validator in the XdsClient class ensures that each endpoint has + * a socket_address with an IP address and a port_value. */ + const socketAddress = lbEndpoint.endpoint!.address!.socket_address!; + return { + host: socketAddress.address!, + port: socketAddress.port_value!, + }; + } + ); + if (addresses.length > 0) { + let localityArray = priorityList[endpoint.priority]; + if (localityArray === undefined) { + localityArray = []; + priorityList[endpoint.priority] = localityArray; + } + localityArray.push({ + locality: endpoint.locality!, + addresses: addresses, + weight: endpoint.load_balancing_weight.value, + }); + newLocalityPriorities.set( + localityToName(endpoint.locality!), + endpoint.priority + ); + } + } + + const newPriorityNames: string[] = []; + const addressList: LocalitySubchannelAddress[] = []; + const priorityChildren: Map = new Map< + string, + PriorityChild + >(); + /* The algorithm here is as follows: for each priority we are given, from + * high to low: + * - If the previous mapping had any of the same localities at the same or + * a lower priority, use the matching name from the highest such + * priority, unless the new mapping has already used that name. + * - Otherwise, construct a new name using this.nextPriorityChildNumber. + */ + for (const [priority, localityArray] of priorityList.entries()) { + // Skip priorities that have no localities with healthy endpoints + if (localityArray === undefined) { + continue; + } + /** + * Highest (smallest number) priority value that any of the localities in + * this locality array had a in the previous mapping. + */ + let highestOldPriority = Infinity; + for (const localityObj of localityArray) { + const oldPriority = this.localityPriorities.get( + localityToName(localityObj.locality) + ); + if ( + oldPriority !== undefined && + oldPriority >= priority && + oldPriority < highestOldPriority + ) { + highestOldPriority = oldPriority; + } + } + let newPriorityName: string; + if (highestOldPriority === Infinity) { + /* No existing priority at or below the same number as the priority we + * are looking at had any of the localities in this priority. So, we + * use a new name. */ + newPriorityName = `child${this.nextPriorityChildNumber++}`; + } else { + const newName = this.priorityNames[highestOldPriority]; + if (newPriorityNames.indexOf(newName) < 0) { + newPriorityName = newName; + } else { + newPriorityName = `child${this.nextPriorityChildNumber++}`; + } + } + newPriorityNames[priority] = newPriorityName; + + const childTargets: Map = new Map< + string, + WeightedTarget + >(); + for (const localityObj of localityArray) { + /* Use the endpoint picking policy from the config, default to + * round_robin. */ + const endpointPickingPolicy: LoadBalancingConfig[] = [ + ...this.lastestConfig.getEndpointPickingPolicy(), + validateLoadBalancingConfig({ round_robin: {} }), + ]; + let childPolicy: LoadBalancingConfig[]; + if (this.lastestConfig.getLrsLoadReportingServerName() !== undefined) { + childPolicy = [new LrsLoadBalancingConfig(this.lastestConfig.getCluster(), this.lastestConfig.getEdsServiceName() ?? '', this.lastestConfig.getLrsLoadReportingServerName()!, localityObj.locality, endpointPickingPolicy)]; + } else { + childPolicy = endpointPickingPolicy; + } + childTargets.set(localityToName(localityObj.locality), { + weight: localityObj.weight, + child_policy: childPolicy, + }); + for (const address of localityObj.addresses) { + addressList.push({ + localityPath: [ + newPriorityName, + localityToName(localityObj.locality), + ], + ...address, + }); + } + } + + const weightedTargetConfig = new WeightedTargetLoadBalancingConfig(childTargets); + let outlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined; + if (EXPERIMENTAL_OUTLIER_DETECTION) { + outlierDetectionConfig = this.lastestConfig.getOutlierDetection()?.copyWithChildPolicy([weightedTargetConfig]); + } + const priorityChildConfig = outlierDetectionConfig ?? weightedTargetConfig; + + priorityChildren.set(newPriorityName, { + config: [priorityChildConfig], + }); + } + /* Contract the priority names array if it is sparse. This config only + * cares about the order of priorities, not their specific numbers */ + const childConfig: PriorityLoadBalancingConfig = new PriorityLoadBalancingConfig(priorityChildren, newPriorityNames.filter((value) => value !== undefined)); + trace('Child update addresses: ' + addressList.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')); + trace('Child update priority config: ' + JSON.stringify(childConfig.toJsonObject(), undefined, 2)); + this.childBalancer.updateAddressList( + addressList, + childConfig, + this.latestAttributes + ); + + this.localityPriorities = newLocalityPriorities; + this.priorityNames = newPriorityNames; + } + + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void { + if (!(lbConfig instanceof EdsLoadBalancingConfig)) { + trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); + return; + } + trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2)); + this.lastestConfig = lbConfig; + this.latestAttributes = attributes; + this.xdsClient = attributes.xdsClient as XdsClient; + const newEdsServiceName = lbConfig.getEdsServiceName() ?? lbConfig.getCluster(); + + /* If the name is changing, disable the old watcher before adding the new + * one */ + if (this.isWatcherActive && this.edsServiceName !== newEdsServiceName) { + trace('Removing old endpoint watcher for edsServiceName ' + this.edsServiceName) + this.xdsClient.removeEndpointWatcher(this.edsServiceName!, this.watcher); + /* Setting isWatcherActive to false here lets us have one code path for + * calling addEndpointWatcher */ + this.isWatcherActive = false; + /* If we have a new name, the latestEdsUpdate does not correspond to + * the new config, so it is no longer valid */ + this.latestEdsUpdate = null; + } + + this.edsServiceName = newEdsServiceName; + + if (!this.isWatcherActive) { + trace('Adding new endpoint watcher for edsServiceName ' + this.edsServiceName); + this.xdsClient.addEndpointWatcher(this.edsServiceName, this.watcher); + this.isWatcherActive = true; + } + + if (lbConfig.getLrsLoadReportingServerName()) { + this.clusterDropStats = this.xdsClient.addClusterDropStats( + lbConfig.getLrsLoadReportingServerName()!, + lbConfig.getCluster(), + lbConfig.getEdsServiceName() ?? '' + ); + } + + /* If updateAddressList is called after receiving an update and the update + * is still valid, we want to update the child config with the information + * in the new EdsLoadBalancingConfig. */ + this.updateChild(); + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + trace('Destroying load balancer with edsServiceName ' + this.edsServiceName); + if (this.edsServiceName) { + this.xdsClient?.removeEndpointWatcher(this.edsServiceName, this.watcher); + } + this.childBalancer.destroy(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, EdsLoadBalancer, EdsLoadBalancingConfig); +} diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts new file mode 100644 index 000000000..9610ea834 --- /dev/null +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -0,0 +1,200 @@ +/* + * Copyright 2020 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. + * + */ + +import { connectivityState as ConnectivityState, StatusObject, status as Status, experimental } from '@grpc/grpc-js'; +import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; +import { XdsClusterLocalityStats, XdsClient, getSingletonXdsClient } from './xds-client'; +import LoadBalancer = experimental.LoadBalancer; +import ChannelControlHelper = experimental.ChannelControlHelper; +import registerLoadBalancerType = experimental.registerLoadBalancerType; +import getFirstUsableConfig = experimental.getFirstUsableConfig; +import SubchannelAddress = experimental.SubchannelAddress; +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import Picker = experimental.Picker; +import PickArgs = experimental.PickArgs; +import PickResultType = experimental.PickResultType; +import PickResult = experimental.PickResult; +import Filter = experimental.Filter; +import BaseFilter = experimental.BaseFilter; +import FilterFactory = experimental.FilterFactory; +import Call = experimental.CallStream; +import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig + +const TYPE_NAME = 'lrs'; + +export class LrsLoadBalancingConfig implements LoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + return { + [TYPE_NAME]: { + cluster_name: this.clusterName, + eds_service_name: this.edsServiceName, + lrs_load_reporting_server_name: this.lrsLoadReportingServerName, + locality: this.locality, + child_policy: this.childPolicy.map(policy => policy.toJsonObject()) + } + } + } + + constructor(private clusterName: string, private edsServiceName: string, private lrsLoadReportingServerName: string, private locality: Locality__Output, private childPolicy: LoadBalancingConfig[]) {} + + getClusterName() { + return this.clusterName; + } + + getEdsServiceName() { + return this.edsServiceName; + } + + getLrsLoadReportingServerName() { + return this.lrsLoadReportingServerName; + } + + getLocality() { + return this.locality; + } + + getChildPolicy() { + return this.childPolicy; + } + + static createFromJson(obj: any): LrsLoadBalancingConfig { + if (!('cluster_name' in obj && typeof obj.cluster_name === 'string')) { + throw new Error('lrs config must have a string field cluster_name'); + } + if (!('eds_service_name' in obj && typeof obj.eds_service_name === 'string')) { + throw new Error('lrs config must have a string field eds_service_name'); + } + if (!('lrs_load_reporting_server_name' in obj && typeof obj.lrs_load_reporting_server_name === 'string')) { + throw new Error('lrs config must have a string field lrs_load_reporting_server_name'); + } + if (!('locality' in obj && obj.locality !== null && typeof obj.locality === 'object')) { + throw new Error('lrs config must have an object field locality'); + } + if ('region' in obj.locality && typeof obj.locality.region !== 'string') { + throw new Error('lrs config locality.region field must be a string if provided'); + } + if ('zone' in obj.locality && typeof obj.locality.zone !== 'string') { + throw new Error('lrs config locality.zone field must be a string if provided'); + } + if ('sub_zone' in obj.locality && typeof obj.locality.sub_zone !== 'string') { + throw new Error('lrs config locality.sub_zone field must be a string if provided'); + } + if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { + throw new Error('lrs config must have a child_policy array'); + } + return new LrsLoadBalancingConfig(obj.cluster_name, obj.eds_service_name, obj.lrs_load_reporting_server_name, { + region: obj.locality.region ?? '', + zone: obj.locality.zone ?? '', + sub_zone: obj.locality.sub_zone ?? '' + }, obj.child_policy.map(validateLoadBalancingConfig)); + } +} + +/** + * Picker that delegates picking to another picker, and reports when calls + * created using those picks start and end. + */ +class LoadReportingPicker implements Picker { + constructor( + private wrappedPicker: Picker, + private localityStatsReporter: XdsClusterLocalityStats + ) {} + + pick(pickArgs: PickArgs): PickResult { + const wrappedPick = this.wrappedPicker.pick(pickArgs); + if (wrappedPick.pickResultType === PickResultType.COMPLETE) { + return { + pickResultType: PickResultType.COMPLETE, + subchannel: wrappedPick.subchannel, + status: null, + onCallStarted: () => { + wrappedPick.onCallStarted?.(); + this.localityStatsReporter.addCallStarted(); + }, + onCallEnded: status => { + wrappedPick.onCallEnded?.(status); + this.localityStatsReporter.addCallFinished(status !== Status.OK); + } + }; + } else { + return wrappedPick; + } + } +} + +/** + * "Load balancer" that delegates the actual load balancing logic to another + * LoadBalancer class and adds hooks to track when calls started using that + * LoadBalancer start and end, and uses the XdsClient to report that + * information back to the xDS server. + */ +export class LrsLoadBalancer implements LoadBalancer { + private childBalancer: ChildLoadBalancerHandler; + private localityStatsReporter: XdsClusterLocalityStats | null = null; + + constructor(private channelControlHelper: ChannelControlHelper) { + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(channelControlHelper, { + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + if (this.localityStatsReporter !== null) { + picker = new LoadReportingPicker(picker, this.localityStatsReporter); + } + channelControlHelper.updateState(connectivityState, picker); + }, + })); + } + + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void { + if (!(lbConfig instanceof LrsLoadBalancingConfig)) { + return; + } + this.localityStatsReporter = (attributes.xdsClient as XdsClient).addClusterLocalityStats( + lbConfig.getLrsLoadReportingServerName(), + lbConfig.getClusterName(), + lbConfig.getEdsServiceName(), + lbConfig.getLocality() + ); + const childPolicy: LoadBalancingConfig = getFirstUsableConfig( + lbConfig.getChildPolicy(), + true + ); + this.childBalancer.updateAddressList(addressList, childPolicy, attributes); + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + this.childBalancer.destroy(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, LrsLoadBalancer, LrsLoadBalancingConfig); +} diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts new file mode 100644 index 000000000..12037a777 --- /dev/null +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -0,0 +1,473 @@ +/* + * Copyright 2020 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. + * + */ + +import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, ChannelOptions } from '@grpc/grpc-js'; +import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; +import LoadBalancer = experimental.LoadBalancer; +import ChannelControlHelper = experimental.ChannelControlHelper; +import getFirstUsableConfig = experimental.getFirstUsableConfig; +import registerLoadBalancerType = experimental.registerLoadBalancerType; +import SubchannelAddress = experimental.SubchannelAddress; +import subchannelAddressToString = experimental.subchannelAddressToString; +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import Picker = experimental.Picker; +import QueuePicker = experimental.QueuePicker; +import UnavailablePicker = experimental.UnavailablePicker; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; + +const TRACER_NAME = 'priority'; + +function trace(text: string): void { + experimental.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'priority'; + +const DEFAULT_FAILOVER_TIME_MS = 10_000; +const DEFAULT_RETENTION_INTERVAL_MS = 15 * 60 * 1000; + +export type LocalitySubchannelAddress = SubchannelAddress & { + localityPath: string[]; +}; + +export function isLocalitySubchannelAddress( + address: SubchannelAddress +): address is LocalitySubchannelAddress { + return Array.isArray((address as LocalitySubchannelAddress).localityPath); +} + +export interface PriorityChild { + config: LoadBalancingConfig[]; +} + +export class PriorityLoadBalancingConfig implements LoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + const childrenField: {[key: string]: object} = {} + for (const [childName, childValue] of this.children.entries()) { + childrenField[childName] = { + config: childValue.config.map(value => value.toJsonObject()) + }; + } + return { + [TYPE_NAME]: { + children: childrenField, + priorities: this.priorities + } + } + } + + constructor(private children: Map, private priorities: string[]) { + } + + getChildren() { + return this.children; + } + + getPriorities() { + return this.priorities; + } + + static createFromJson(obj: any): PriorityLoadBalancingConfig { + if (!('children' in obj && obj.children !== null && typeof obj.children === 'object')) { + throw new Error('Priority config must have a children map'); + } + if (!('priorities' in obj && Array.isArray(obj.priorities) && (obj.priorities as any[]).every(value => typeof value === 'string'))) { + throw new Error('Priority config must have a priorities list'); + } + const childrenMap: Map = new Map(); + for (const childName of obj.children) { + const childObj = obj.children[childName] + if (!('config' in childObj && Array.isArray(childObj.config))) { + throw new Error(`Priority child ${childName} must have a config list`); + } + childrenMap.set(childName, { + config: childObj.config.map(validateLoadBalancingConfig) + }); + } + return new PriorityLoadBalancingConfig(childrenMap, obj.priorities); + } +} + +interface PriorityChildBalancer { + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void; + exitIdle(): void; + resetBackoff(): void; + deactivate(): void; + maybeReactivate(): void; + isFailoverTimerPending(): boolean; + getConnectivityState(): ConnectivityState; + getPicker(): Picker; + getName(): string; + destroy(): void; +} + +interface UpdateArgs { + subchannelAddress: SubchannelAddress[]; + lbConfig: LoadBalancingConfig; +} + +export class PriorityLoadBalancer implements LoadBalancer { + /** + * Inner class for holding a child priority and managing associated timers. + */ + private PriorityChildImpl = class implements PriorityChildBalancer { + private connectivityState: ConnectivityState = ConnectivityState.IDLE; + private picker: Picker; + private childBalancer: ChildLoadBalancerHandler; + private failoverTimer: NodeJS.Timer | null = null; + private deactivationTimer: NodeJS.Timer | null = null; + private seenReadyOrIdleSinceTransientFailure = false; + constructor(private parent: PriorityLoadBalancer, private name: string) { + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.parent.channelControlHelper, { + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + this.updateState(connectivityState, picker); + }, + })); + this.picker = new QueuePicker(this.childBalancer); + this.startFailoverTimer(); + } + + private updateState(connectivityState: ConnectivityState, picker: Picker) { + trace('Child ' + this.name + ' ' + ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[connectivityState]); + this.connectivityState = connectivityState; + this.picker = picker; + if (connectivityState === ConnectivityState.CONNECTING) { + if (this.seenReadyOrIdleSinceTransientFailure && this.failoverTimer === null) { + this.startFailoverTimer(); + } + } else if (connectivityState === ConnectivityState.READY || connectivityState === ConnectivityState.IDLE) { + this.seenReadyOrIdleSinceTransientFailure = true; + this.cancelFailoverTimer(); + } else if (connectivityState === ConnectivityState.TRANSIENT_FAILURE) { + this.seenReadyOrIdleSinceTransientFailure = false; + this.cancelFailoverTimer(); + } + this.parent.onChildStateChange(this); + } + + private startFailoverTimer() { + if (this.failoverTimer === null) { + trace('Starting failover timer for child ' + this.name); + this.failoverTimer = setTimeout(() => { + trace('Failover timer triggered for child ' + this.name); + this.failoverTimer = null; + this.updateState( + ConnectivityState.TRANSIENT_FAILURE, + new UnavailablePicker() + ); + }, DEFAULT_FAILOVER_TIME_MS); + } + } + + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void { + this.childBalancer.updateAddressList(addressList, lbConfig, attributes); + } + + exitIdle() { + this.childBalancer.exitIdle(); + } + + resetBackoff() { + this.childBalancer.resetBackoff(); + } + + deactivate() { + if (this.deactivationTimer === null) { + this.deactivationTimer = setTimeout(() => { + this.parent.deleteChild(this); + this.childBalancer.destroy(); + }, DEFAULT_RETENTION_INTERVAL_MS); + } + } + + maybeReactivate() { + if (this.deactivationTimer !== null) { + clearTimeout(this.deactivationTimer); + this.deactivationTimer = null; + } + } + + private cancelFailoverTimer() { + if (this.failoverTimer !== null) { + clearTimeout(this.failoverTimer); + this.failoverTimer = null; + } + } + + isFailoverTimerPending() { + return this.failoverTimer !== null; + } + + getConnectivityState() { + return this.connectivityState; + } + + getPicker() { + return this.picker; + } + + getName() { + return this.name; + } + + destroy() { + this.childBalancer.destroy(); + } + }; + // End of inner class PriorityChildImpl + + private children: Map = new Map< + string, + PriorityChildBalancer + >(); + /** + * The priority order of child names from the latest config update. + */ + private priorities: string[] = []; + /** + * The attributes object from the latest update, saved to be passed along to + * each new child as they are created + */ + private latestAttributes: { [key: string]: unknown } = {}; + /** + * The latest load balancing policies and address lists for each child from + * the latest update + */ + private latestUpdates: Map = new Map< + string, + UpdateArgs + >(); + /** + * Current chosen priority that requests are sent to + */ + private currentPriority: number | null = null; + + private updatesPaused = false; + + constructor(private channelControlHelper: ChannelControlHelper) {} + + private updateState(state: ConnectivityState, picker: Picker) { + trace( + 'Transitioning to ' + + ConnectivityState[state] + ); + /* If switching to IDLE, use a QueuePicker attached to this load balancer + * so that when the picker calls exitIdle, that in turn calls exitIdle on + * the PriorityChildImpl, which will start the failover timer. */ + if (state === ConnectivityState.IDLE) { + picker = new QueuePicker(this); + } + this.channelControlHelper.updateState(state, picker); + } + + private onChildStateChange(child: PriorityChildBalancer) { + const childState = child.getConnectivityState(); + trace('Child ' + child.getName() + ' transitioning to ' + ConnectivityState[childState]); + if (this.updatesPaused) { + return; + } + this.choosePriority(); + } + + private deleteChild(child: PriorityChildBalancer) { + this.children.delete(child.getName()); + } + + /** + * Select the child at the specified priority, and report that child's state + * as this balancer's state until that child disconnects or a higher-priority + * child connects. + * @param priority + */ + private selectPriority(priority: number, deactivateLowerPriorities: boolean) { + this.currentPriority = priority; + const chosenChild = this.children.get(this.priorities[priority])!; + this.updateState( + chosenChild.getConnectivityState(), + chosenChild.getPicker() + ); + if (deactivateLowerPriorities) { + for (let i = priority + 1; i < this.priorities.length; i++) { + this.children.get(this.priorities[i])?.deactivate(); + } + } + } + + private choosePriority() { + if (this.priorities.length === 0) { + this.updateState(ConnectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: Status.UNAVAILABLE, details: 'priority policy has empty priority list', metadata: new Metadata()})); + return; + } + + for (const [priority, childName] of this.priorities.entries()) { + trace('Trying priority ' + priority + ' child ' + childName); + let child = this.children.get(childName); + /* If the child doesn't already exist, create it and update it. */ + if (child === undefined) { + child = new this.PriorityChildImpl(this, childName); + this.children.set(childName, child); + const childUpdate = this.latestUpdates.get(childName); + if (childUpdate !== undefined) { + child.updateAddressList( + childUpdate.subchannelAddress, + childUpdate.lbConfig, + this.latestAttributes + ); + } + } else { + /* We're going to try to use this child, so reactivate it if it has been + * deactivated */ + child.maybeReactivate(); + } + if ( + child.getConnectivityState() === ConnectivityState.READY || + child.getConnectivityState() === ConnectivityState.IDLE + ) { + this.selectPriority(priority, true); + return; + } + if (child.isFailoverTimerPending()) { + this.selectPriority(priority, false); + /* This child is still trying to connect. Wait until its failover timer + * has ended to continue to the next one */ + return; + } + } + + /* If we didn't find any priority to try, pick the first one in the state + * CONNECTING */ + for (const [priority, childName] of this.priorities.entries()) { + let child = this.children.get(childName)!; + if (child.getConnectivityState() === ConnectivityState.CONNECTING) { + this.selectPriority(priority, false); + return; + } + } + + // Did not find any child in CONNECTING, delegate to last child + this.selectPriority(this.priorities.length - 1, false); + } + + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void { + if (!(lbConfig instanceof PriorityLoadBalancingConfig)) { + // Reject a config of the wrong type + trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); + return; + } + /* For each address, the first element of its localityPath array determines + * which child it belongs to. So we bucket those addresses by that first + * element, and pass along the rest of the localityPath for that child + * to use. */ + const childAddressMap: Map = new Map< + string, + LocalitySubchannelAddress[] + >(); + for (const address of addressList) { + if (!isLocalitySubchannelAddress(address)) { + // Reject address that cannot be prioritized + return; + } + if (address.localityPath.length < 1) { + // Reject address that cannot be prioritized + return; + } + const childName = address.localityPath[0]; + const childAddress: LocalitySubchannelAddress = { + ...address, + localityPath: address.localityPath.slice(1), + }; + let childAddressList = childAddressMap.get(childName); + if (childAddressList === undefined) { + childAddressList = []; + childAddressMap.set(childName, childAddressList); + } + childAddressList.push(childAddress); + } + this.latestAttributes = attributes; + this.latestUpdates.clear(); + this.priorities = lbConfig.getPriorities(); + this.updatesPaused = true; + /* Pair up the new child configs with the corresponding address lists, and + * update all existing children with their new configs */ + for (const [childName, childConfig] of lbConfig.getChildren()) { + const chosenChildConfig = getFirstUsableConfig(childConfig.config); + if (chosenChildConfig !== null) { + const childAddresses = childAddressMap.get(childName) ?? []; + trace('Assigning child ' + childName + ' address list ' + childAddresses.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')) + this.latestUpdates.set(childName, { + subchannelAddress: childAddresses, + lbConfig: chosenChildConfig, + }); + const existingChild = this.children.get(childName); + if (existingChild !== undefined) { + existingChild.updateAddressList( + childAddresses, + chosenChildConfig, + attributes + ); + } + } + } + // Deactivate all children that are no longer in the priority list + for (const [childName, child] of this.children) { + if (this.priorities.indexOf(childName) < 0) { + trace('Deactivating child ' + childName); + child.deactivate(); + } + } + this.updatesPaused = false; + this.choosePriority(); + } + exitIdle(): void { + if (this.currentPriority !== null) { + this.children.get(this.priorities[this.currentPriority])?.exitIdle(); + } + } + resetBackoff(): void { + for (const child of this.children.values()) { + child.resetBackoff(); + } + } + destroy(): void { + for (const child of this.children.values()) { + child.destroy(); + } + this.children.clear(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, PriorityLoadBalancer, PriorityLoadBalancingConfig); +} diff --git a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts new file mode 100644 index 000000000..7cd92d98b --- /dev/null +++ b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts @@ -0,0 +1,387 @@ +/* + * Copyright 2020 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. + * + */ + +import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental } from "@grpc/grpc-js"; +import { isLocalitySubchannelAddress, LocalitySubchannelAddress } from "./load-balancer-priority"; +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import LoadBalancer = experimental.LoadBalancer; +import ChannelControlHelper = experimental.ChannelControlHelper; +import getFirstUsableConfig = experimental.getFirstUsableConfig; +import registerLoadBalancerType = experimental.registerLoadBalancerType; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import Picker = experimental.Picker; +import PickResult = experimental.PickResult; +import PickArgs = experimental.PickArgs; +import QueuePicker = experimental.QueuePicker; +import UnavailablePicker = experimental.UnavailablePicker; +import SubchannelAddress = experimental.SubchannelAddress; +import subchannelAddressToString = experimental.subchannelAddressToString; +import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; + +const TRACER_NAME = 'weighted_target'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'weighted_target'; + +const DEFAULT_RETENTION_INTERVAL_MS = 15 * 60 * 1000; + + export interface WeightedTarget { + weight: number; + child_policy: LoadBalancingConfig[]; +} + +export class WeightedTargetLoadBalancingConfig implements LoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + + constructor(private targets: Map) { + } + + getTargets() { + return this.targets; + } + + toJsonObject(): object { + const targetsField: {[key: string]: object} = {}; + for (const [targetName, targetValue] of this.targets.entries()) { + targetsField[targetName] = { + weight: targetValue.weight, + child_policy: targetValue.child_policy.map(policy => policy.toJsonObject()) + }; + } + return { + [TYPE_NAME]: { + targets: targetsField + } + } + } + + static createFromJson(obj: any): WeightedTargetLoadBalancingConfig { + const targetsMap: Map = new Map(); + if (!('targets' in obj && obj.targets !== null && typeof obj.targets === 'object')) { + throw new Error('Weighted target config must have a targets map'); + } + for (const key of obj.targets) { + const targetObj = obj.targets[key]; + if (!('weight' in targetObj && typeof targetObj.weight === 'number')) { + throw new Error(`Weighted target ${key} must have a numeric weight`); + } + if (!('child_policy' in targetObj && Array.isArray(targetObj.child_policy))) { + throw new Error(`Weighted target ${key} must have a child_policy array`); + } + const validatedTarget: WeightedTarget = { + weight: targetObj.weight, + child_policy: targetObj.child_policy.map(validateLoadBalancingConfig) + } + targetsMap.set(key, validatedTarget); + } + return new WeightedTargetLoadBalancingConfig(targetsMap); + } +} + +/** + * Represents a picker and a subinterval of a larger interval used for randomly + * selecting an element of a list of these objects. + */ +interface WeightedPicker { + picker: Picker; + /** + * The exclusive end of the interval associated with this element. The start + * of the interval is implicitly the rangeEnd of the previous element in the + * list, or 0 for the first element in the list. + */ + rangeEnd: number; +} + +class WeightedTargetPicker implements Picker { + private rangeTotal: number; + constructor(private readonly pickerList: WeightedPicker[]) { + this.rangeTotal = pickerList[pickerList.length - 1].rangeEnd; + } + pick(pickArgs: PickArgs): PickResult { + // num | 0 is equivalent to floor(num) + const selection = (Math.random() * this.rangeTotal) | 0; + + for (const entry of this.pickerList) { + if (selection < entry.rangeEnd) { + return entry.picker.pick(pickArgs); + } + } + + /* Default to first element if the iteration doesn't find anything for some + * reason. */ + return this.pickerList[0].picker.pick(pickArgs); + } +} + +interface WeightedChild { + updateAddressList(addressList: SubchannelAddress[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void; + exitIdle(): void; + resetBackoff(): void; + destroy(): void; + deactivate(): void; + maybeReactivate(): void; + getConnectivityState(): ConnectivityState; + getPicker(): Picker; + getWeight(): number; +} + +export class WeightedTargetLoadBalancer implements LoadBalancer { + private WeightedChildImpl = class implements WeightedChild { + private connectivityState: ConnectivityState = ConnectivityState.IDLE; + private picker: Picker; + private childBalancer: ChildLoadBalancerHandler; + private deactivationTimer: NodeJS.Timer | null = null; + private weight: number = 0; + + constructor(private parent: WeightedTargetLoadBalancer, private name: string) { + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.parent.channelControlHelper, { + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + this.updateState(connectivityState, picker); + }, + })); + + this.picker = new QueuePicker(this.childBalancer); + } + + private updateState(connectivityState: ConnectivityState, picker: Picker) { + trace('Target ' + this.name + ' ' + ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[connectivityState]); + this.connectivityState = connectivityState; + this.picker = picker; + this.parent.maybeUpdateState(); + } + + updateAddressList(addressList: SubchannelAddress[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void { + this.weight = lbConfig.weight; + const childConfig = getFirstUsableConfig(lbConfig.child_policy); + if (childConfig !== null) { + this.childBalancer.updateAddressList(addressList, childConfig, attributes); + } + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + this.childBalancer.destroy(); + if (this.deactivationTimer !== null) { + clearTimeout(this.deactivationTimer); + } + } + deactivate(): void { + if (this.deactivationTimer === null) { + this.deactivationTimer = setTimeout(() => { + this.parent.targets.delete(this.name); + this.deactivationTimer = null; + }, DEFAULT_RETENTION_INTERVAL_MS); + } + } + maybeReactivate(): void { + if (this.deactivationTimer !== null) { + clearTimeout(this.deactivationTimer); + this.deactivationTimer = null; + } + } + getConnectivityState(): ConnectivityState { + return this.connectivityState; + } + getPicker(): Picker { + return this.picker; + } + getWeight(): number { + return this.weight; + } + } + // end of WeightedChildImpl + + /** + * Map of target names to target children. Includes current targets and + * previous targets with deactivation timers that have not yet triggered. + */ + private targets: Map = new Map(); + /** + * List of current target names. + */ + private targetList: string[] = []; + private updatesPaused = false; + + constructor(private channelControlHelper: ChannelControlHelper) {} + + private maybeUpdateState() { + if (!this.updatesPaused) { + this.updateState() + } + } + + private updateState() { + const pickerList: WeightedPicker[] = []; + let end = 0; + + let connectingCount = 0; + let idleCount = 0; + let transientFailureCount = 0; + for (const targetName of this.targetList) { + const target = this.targets.get(targetName); + if (target === undefined) { + continue; + } + switch (target.getConnectivityState()) { + case ConnectivityState.READY: + end += target.getWeight(); + pickerList.push({ + picker: target.getPicker(), + rangeEnd: end + }); + break; + case ConnectivityState.CONNECTING: + connectingCount += 1; + break; + case ConnectivityState.IDLE: + idleCount += 1; + break; + case ConnectivityState.TRANSIENT_FAILURE: + transientFailureCount += 1; + break; + default: + // Ignore the other possiblity, SHUTDOWN + } + } + + let connectivityState: ConnectivityState; + if (pickerList.length > 0) { + connectivityState = ConnectivityState.READY; + } else if (connectingCount > 0) { + connectivityState = ConnectivityState.CONNECTING; + } else if (idleCount > 0) { + connectivityState = ConnectivityState.IDLE; + } else { + connectivityState = ConnectivityState.TRANSIENT_FAILURE; + } + + let picker: Picker; + switch (connectivityState) { + case ConnectivityState.READY: + picker = new WeightedTargetPicker(pickerList); + break; + case ConnectivityState.CONNECTING: + case ConnectivityState.IDLE: + picker = new QueuePicker(this); + break; + default: + picker = new UnavailablePicker({ + code: Status.UNAVAILABLE, + details: 'weighted_target: all children report state TRANSIENT_FAILURE', + metadata: new Metadata() + }); + } + trace( + 'Transitioning to ' + + ConnectivityState[connectivityState] + ); + this.channelControlHelper.updateState(connectivityState, picker); + } + + updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof WeightedTargetLoadBalancingConfig)) { + // Reject a config of the wrong type + trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); + return; + } + + /* For each address, the first element of its localityPath array determines + * which child it belongs to. So we bucket those addresses by that first + * element, and pass along the rest of the localityPath for that child + * to use. */ + const childAddressMap = new Map(); + for (const address of addressList) { + if (!isLocalitySubchannelAddress(address)) { + // Reject address that cannot be associated with targets + return; + } + if (address.localityPath.length < 1) { + // Reject address that cannot be associated with targets + return; + } + const childName = address.localityPath[0]; + const childAddress: LocalitySubchannelAddress = { + ...address, + localityPath: address.localityPath.slice(1), + }; + let childAddressList = childAddressMap.get(childName); + if (childAddressList === undefined) { + childAddressList = []; + childAddressMap.set(childName, childAddressList); + } + childAddressList.push(childAddress); + } + + this.updatesPaused = true; + this.targetList = Array.from(lbConfig.getTargets().keys()); + for (const [targetName, targetConfig] of lbConfig.getTargets()) { + let target = this.targets.get(targetName); + if (target === undefined) { + target = new this.WeightedChildImpl(this, targetName); + this.targets.set(targetName, target); + } else { + target.maybeReactivate(); + } + const targetAddresses = childAddressMap.get(targetName) ?? []; + trace('Assigning target ' + targetName + ' address list ' + targetAddresses.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')); + target.updateAddressList(targetAddresses, targetConfig, attributes); + } + + // Deactivate targets that are not in the new config + for (const [targetName, target] of this.targets) { + if (this.targetList.indexOf(targetName) < 0) { + trace('Deactivating target ' + targetName); + target.deactivate(); + } + } + this.updatesPaused = false; + + this.updateState(); + } + exitIdle(): void { + for (const targetName of this.targetList) { + this.targets.get(targetName)?.exitIdle(); + } + } + resetBackoff(): void { + for (const targetName of this.targetList) { + this.targets.get(targetName)?.resetBackoff(); + } + } + destroy(): void { + for (const target of this.targets.values()) { + target.destroy(); + } + this.targets.clear(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, WeightedTargetLoadBalancer, WeightedTargetLoadBalancingConfig); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts new file mode 100644 index 000000000..bfdb4dccc --- /dev/null +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -0,0 +1,299 @@ +/* + * Copyright 2020 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. + * + */ + +import { connectivityState as ConnectivityState, status as Status, experimental, logVerbosity, Metadata, status } from "@grpc/grpc-js/"; + +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; +import LoadBalancer = experimental.LoadBalancer; +import Picker = experimental.Picker; +import PickResult = experimental.PickResult; +import PickArgs = experimental.PickArgs; +import PickResultType = experimental.PickResultType; +import UnavailablePicker = experimental.UnavailablePicker; +import QueuePicker = experimental.QueuePicker; +import SubchannelAddress = experimental.SubchannelAddress; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import getFirstUsableConfig = experimental.getFirstUsableConfig; +import ChannelControlHelper = experimental.ChannelControlHelper; +import registerLoadBalancerType = experimental.registerLoadBalancerType; + +const TRACER_NAME = 'xds_cluster_manager'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'xds_cluster_manager'; + +interface ClusterManagerChild { + child_policy: LoadBalancingConfig[]; +} + +export class XdsClusterManagerLoadBalancingConfig implements LoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + + constructor(private children: Map) {} + + getChildren() { + return this.children; + } + + toJsonObject(): object { + const childrenField: {[key: string]: object} = {}; + for (const [childName, childValue] of this.children.entries()) { + childrenField[childName] = { + child_policy: childValue.child_policy.map(policy => policy.toJsonObject()) + }; + } + return { + [TYPE_NAME]: { + children: childrenField + } + } + } + + static createFromJson(obj: any): XdsClusterManagerLoadBalancingConfig { + const childrenMap: Map = new Map(); + if (!('children' in obj && obj.children !== null && typeof obj.children === 'object')) { + throw new Error('xds_cluster_manager config must have a children map'); + } + for (const key of obj.children) { + const childObj = obj.children[key]; + if (!('child_policy' in childObj && Array.isArray(childObj.child_policy))) { + throw new Error(`xds_cluster_manager child ${key} must have a child_policy array`); + } + const validatedChild = { + child_policy: childObj.child_policy.map(validateLoadBalancingConfig) + }; + childrenMap.set(key, validatedChild); + } + return new XdsClusterManagerLoadBalancingConfig(childrenMap); + } +} + +class XdsClusterManagerPicker implements Picker { + constructor(private childPickers: Map) {} + + pick(pickArgs: PickArgs): PickResult { + /* extraPickInfo.cluster should be set for all calls by the config selector + * corresponding to the service config that specified the use of this LB + * policy. */ + const cluster = pickArgs.extraPickInfo.cluster ?? ''; + if (this.childPickers.has(cluster)) { + return this.childPickers.get(cluster)!.pick(pickArgs); + } else { + return { + pickResultType: PickResultType.TRANSIENT_FAILURE, + status: { + code: status.INTERNAL, + details: `Requested cluster ${cluster} not found`, + metadata: new Metadata(), + }, + subchannel: null, + onCallStarted: null, + onCallEnded: null + }; + } + } +} + +interface XdsClusterManagerChild { + updateAddressList(addressList: SubchannelAddress[], lbConfig: ClusterManagerChild, attributes: { [key: string]: unknown; }): void; + exitIdle(): void; + resetBackoff(): void; + destroy(): void; + getConnectivityState(): ConnectivityState; + getPicker(): Picker; + +} + +class XdsClusterManager implements LoadBalancer { + private XdsClusterManagerChildImpl = class implements XdsClusterManagerChild { + private connectivityState: ConnectivityState = ConnectivityState.IDLE; + private picker: Picker; + private childBalancer: ChildLoadBalancerHandler; + + constructor(private parent: XdsClusterManager, private name: string) { + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.parent.channelControlHelper, { + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + this.updateState(connectivityState, picker); + }, + })); + + this.picker = new QueuePicker(this.childBalancer); + } + + private updateState(connectivityState: ConnectivityState, picker: Picker) { + trace('Child ' + this.name + ' ' + ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[connectivityState]); + this.connectivityState = connectivityState; + this.picker = picker; + this.parent.maybeUpdateState(); + } + updateAddressList(addressList: SubchannelAddress[], lbConfig: ClusterManagerChild, attributes: { [key: string]: unknown; }): void { + const childConfig = getFirstUsableConfig(lbConfig.child_policy); + if (childConfig !== null) { + this.childBalancer.updateAddressList(addressList, childConfig, attributes); + } + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + this.childBalancer.destroy(); + } + getConnectivityState(): ConnectivityState { + return this.connectivityState; + } + getPicker(): Picker { + return this.picker; + } + } + // End of XdsClusterManagerChildImpl + + private children: Map = new Map(); + // Shutdown is a placeholder value that will never appear in normal operation. + private currentState: ConnectivityState = ConnectivityState.SHUTDOWN; + private updatesPaused = false; + constructor(private channelControlHelper: ChannelControlHelper) {} + + private maybeUpdateState() { + if (!this.updatesPaused) { + this.updateState(); + } + } + + private updateState() { + const pickerMap: Map = new Map(); + let anyReady = false; + let anyConnecting = false; + let anyIdle = false; + for (const [name, child] of this.children.entries()) { + pickerMap.set(name, child.getPicker()); + switch (child.getConnectivityState()) { + case ConnectivityState.READY: + anyReady = true; + break; + case ConnectivityState.CONNECTING: + anyConnecting = true; + break; + case ConnectivityState.IDLE: + anyIdle = true; + break; + } + } + let connectivityState: ConnectivityState; + if (anyReady) { + connectivityState = ConnectivityState.READY; + } else if (anyConnecting) { + connectivityState = ConnectivityState.CONNECTING; + } else if (anyIdle) { + connectivityState = ConnectivityState.IDLE; + } else { + connectivityState = ConnectivityState.TRANSIENT_FAILURE; + } + /* For each of the states CONNECTING, IDLE, and TRANSIENT_FAILURE, there is + * exactly one corresponding picker, so if the state is one of those and + * that does not change, no new information is provided by passing the + * new state upward. */ + if (connectivityState === this.currentState && connectivityState !== ConnectivityState.READY) { + return; + } + let picker: Picker; + + switch (connectivityState) { + case ConnectivityState.READY: + picker = new XdsClusterManagerPicker(pickerMap); + break; + case ConnectivityState.CONNECTING: + case ConnectivityState.IDLE: + picker = new QueuePicker(this); + break; + default: + picker = new UnavailablePicker({ + code: Status.UNAVAILABLE, + details: 'xds_cluster_manager: all children report state TRANSIENT_FAILURE', + metadata: new Metadata() + }); + } + trace( + 'Transitioning to ' + + ConnectivityState[connectivityState] + ); + this.channelControlHelper.updateState(connectivityState, picker); + } + + updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof XdsClusterManagerLoadBalancingConfig)) { + // Reject a config of the wrong type + trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); + return; + } + trace('Received update with config: ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); + const configChildren = lbConfig.getChildren(); + // Delete children that are not in the new config + const namesToRemove: string[] = []; + for (const name of this.children.keys()) { + if (!configChildren.has(name)) { + namesToRemove.push(name); + } + } + this.updatesPaused = true; + for (const name of namesToRemove) { + this.children.get(name)!.destroy(); + this.children.delete(name); + } + // Add new children that were not in the previous config + for (const [name, childConfig] of configChildren.entries()) { + if (!this.children.has(name)) { + const newChild = new this.XdsClusterManagerChildImpl(this, name); + newChild.updateAddressList(addressList, childConfig, attributes); + this.children.set(name, newChild); + } + } + this.updatesPaused = false; + this.updateState(); + } + exitIdle(): void { + for (const child of this.children.values()) { + child.exitIdle(); + } + } + resetBackoff(): void { + for (const child of this.children.values()) { + child.resetBackoff(); + } + } + destroy(): void { + for (const child of this.children.values()) { + child.destroy(); + } + this.children.clear(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, XdsClusterManager, XdsClusterManagerLoadBalancingConfig); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/matcher.ts b/packages/grpc-js-xds/src/matcher.ts new file mode 100644 index 000000000..b173c30e0 --- /dev/null +++ b/packages/grpc-js-xds/src/matcher.ts @@ -0,0 +1,237 @@ +/* + * Copyright 2021 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. + */ + +import { Metadata } from "@grpc/grpc-js"; +import { RE2 } from "re2-wasm"; +import { Fraction, fractionToString } from "./fraction"; + +/** + * An object representing a predicate that determines whether a given + * combination of a methodName and metadata matches some internal conditions. + */ +export interface Matcher { + toString(): string; + apply(methodName: string, metadata: Metadata): boolean; +} + +/** + * An object representing a predicate that determines whether a given string + * value matches some internal conditions. + */ +export interface ValueMatcher { + toString(): string; + apply(value: string): boolean; +} + +export class ExactValueMatcher implements ValueMatcher { + constructor(private targetValue: string) {} + + apply(value: string) { + return value === this.targetValue; + } + + toString() { + return 'Exact(' + this.targetValue + ')'; + } +} + +export class SafeRegexValueMatcher implements ValueMatcher { + private targetRegexImpl: RE2; + constructor(targetRegex: string) { + this.targetRegexImpl = new RE2(`^${targetRegex}$`, 'u'); + } + + apply(value: string) { + return this.targetRegexImpl.test(value); + } + + toString() { + return 'SafeRegex(' + this.targetRegexImpl.toString() + ')'; + } +} + +const numberRegex = new RE2(/^-?\d+$/u); + +export class RangeValueMatcher implements ValueMatcher { + constructor(private start: BigInt, private end: BigInt) {} + + apply(value: string) { + if (!numberRegex.test(value)) { + return false; + } + const numberValue = BigInt(value); + return this.start <= numberValue && numberValue < this.end; + } + + toString() { + return 'Range(' + this.start + ', ' + this.end + ')'; + } +} + +export class PresentValueMatcher implements ValueMatcher { + constructor() {} + + apply(value: string) { + return true; + } + + toString() { + return 'Present()'; + } +} + +export class PrefixValueMatcher implements ValueMatcher { + constructor(private prefix: string) {} + + apply(value: string) { + return value.startsWith(this.prefix); + } + + toString() { + return 'Prefix(' + this.prefix + ')'; + } +} + +export class SuffixValueMatcher implements ValueMatcher { + constructor(private suffix: string) {} + + apply(value: string) { + return value.endsWith(this.suffix); + } + + toString() { + return 'Suffix(' + this.suffix + ')'; + } +} + +export class RejectValueMatcher implements ValueMatcher { + constructor() {} + + apply(value: string) { + return false; + } + + toString() { + return 'Reject()'; + } +} + +export class HeaderMatcher implements Matcher { + constructor(private headerName: string, private valueMatcher: ValueMatcher, private invertMatch: boolean) {} + + private applyHelper(methodName: string, metadata: Metadata) { + if (this.headerName.endsWith('-bin')) { + return false; + } + let value: string; + if (this.headerName === 'content-type') { + value = 'application/grpc'; + } else { + const valueArray = metadata.get(this.headerName); + if (valueArray.length === 0) { + return false; + } else { + value = valueArray.join(','); + } + } + return this.valueMatcher.apply(value); + } + + apply(methodName: string, metadata: Metadata) { + const result = this.applyHelper(methodName, metadata); + if (this.invertMatch) { + return !result; + } else { + return result; + } + } + + toString() { + return 'HeaderMatch(' + this.headerName + ', ' + this.valueMatcher.toString() + ')'; + } +} + +export class PathPrefixValueMatcher { + constructor(private prefix: string, private caseInsensitive: boolean) {} + + apply(value: string) { + if (this.caseInsensitive) { + return value.toLowerCase().startsWith(this.prefix.toLowerCase()); + } else { + return value.startsWith(this.prefix); + } + } + + toString() { + return 'Prefix(' + this.prefix + ', ' + this.caseInsensitive + ')'; + } +} + +export class PathExactValueMatcher { + constructor(private targetValue: string, private caseInsensitive: boolean) {} + + apply(value: string) { + if (this.caseInsensitive) { + return value.toLowerCase().startsWith(this.targetValue.toLowerCase()); + } else { + return value === this.targetValue; + } + } + + toString() { + return 'Exact(' + this.targetValue + ', ' + this.caseInsensitive + ')'; + } +} + +export class PathSafeRegexValueMatcher { + private targetRegexImpl: RE2; + constructor(targetRegex: string) { + this.targetRegexImpl = new RE2(`^${targetRegex}$`, 'u'); + } + + apply(value: string) { + return this.targetRegexImpl.test(value); + } + + toString() { + return 'SafeRegex(' + this.targetRegexImpl.toString() + ')'; + } +} + +export class FullMatcher implements Matcher { + constructor(private pathMatcher: ValueMatcher, private headerMatchers: Matcher[], private fraction: Fraction | null) {} + + apply(methodName: string, metadata: Metadata) { + if (!this.pathMatcher.apply(methodName)) { + return false; + } + if (!this.headerMatchers.every(matcher => matcher.apply(methodName, metadata))) { + return false; + } + if (this.fraction === null) { + return true; + } else { + const randomNumber = Math.random() * this.fraction.denominator; + return randomNumber < this.fraction.numerator; + } + } + + toString() { + return `path: ${this.pathMatcher} + headers: ${this.headerMatchers.map(matcher => matcher.toString()).join('\n\t')} + fraction: ${this.fraction ? fractionToString(this.fraction): 'none'}`; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/protobuf-any.ts b/packages/grpc-js-xds/src/protobuf-any.ts new file mode 100644 index 000000000..cfee35f91 --- /dev/null +++ b/packages/grpc-js-xds/src/protobuf-any.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2021 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. + */ + +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { Any__Output } from './generated/google/protobuf/Any'; + +function parseAnyMessage(encodedMessage: Any__Output) { + +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts new file mode 100644 index 000000000..9879a2c6e --- /dev/null +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -0,0 +1,597 @@ +/* + * Copyright 2019 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. + */ + +import * as protoLoader from '@grpc/proto-loader'; + +import { RE2 } from 're2-wasm'; + +import { getSingletonXdsClient, XdsClient } from './xds-client'; +import { StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions } from '@grpc/grpc-js'; +import Resolver = experimental.Resolver; +import GrpcUri = experimental.GrpcUri; +import ResolverListener = experimental.ResolverListener; +import uriToString = experimental.uriToString; +import ServiceConfig = experimental.ServiceConfig; +import registerResolver = experimental.registerResolver; +import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; +import { Watcher } from './xds-stream-state/xds-stream-state'; +import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; +import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; +import { CdsLoadBalancingConfig } from './load-balancer-cds'; +import { VirtualHost__Output } from './generated/envoy/config/route/v3/VirtualHost'; +import { RouteMatch__Output } from './generated/envoy/config/route/v3/RouteMatch'; +import { HeaderMatcher__Output } from './generated/envoy/config/route/v3/HeaderMatcher'; +import ConfigSelector = experimental.ConfigSelector; +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import { XdsClusterManagerLoadBalancingConfig } from './load-balancer-xds-cluster-manager'; +import { ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; +import { envoyFractionToFraction, Fraction } from "./fraction"; +import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; +import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resources'; +import Duration = experimental.Duration; +import { Duration__Output } from './generated/google/protobuf/Duration'; +import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter'; +import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from './environment'; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import RetryPolicy = experimental.RetryPolicy; +import { validateBootstrapConfig } from './xds-bootstrap'; + +const TRACER_NAME = 'xds_resolver'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +// Better match type has smaller value. +enum MatchType { + EXACT_MATCH, + SUFFIX_MATCH, + PREFIX_MATCH, + UNIVERSE_MATCH, + INVALID_MATCH, +}; + +function domainPatternMatchType(domainPattern: string): MatchType { + if (domainPattern.length === 0) { + return MatchType.INVALID_MATCH; + } + if (domainPattern.indexOf('*') < 0) { + return MatchType.EXACT_MATCH; + } + if (domainPattern === '*') { + return MatchType.UNIVERSE_MATCH; + } + if (domainPattern.startsWith('*')) { + return MatchType.SUFFIX_MATCH; + } + if (domainPattern.endsWith('*')) { + return MatchType.PREFIX_MATCH; + } + return MatchType.INVALID_MATCH; +} + +function domainMatch(matchType: MatchType, domainPattern: string, expectedHostName: string) { + switch (matchType) { + case MatchType.EXACT_MATCH: + return expectedHostName === domainPattern; + case MatchType.SUFFIX_MATCH: + return expectedHostName.endsWith(domainPattern.substring(1)); + case MatchType.PREFIX_MATCH: + return expectedHostName.startsWith(domainPattern.substring(0, domainPattern.length - 1)); + case MatchType.UNIVERSE_MATCH: + return true; + case MatchType.INVALID_MATCH: + return false; + } +} + +function findVirtualHostForDomain(virutalHostList: VirtualHost__Output[], domain: string): VirtualHost__Output | null { + let targetVhost: VirtualHost__Output | null = null; + let bestMatchType: MatchType = MatchType.INVALID_MATCH; + let longestMatch = 0; + for (const virtualHost of virutalHostList) { + for (const domainPattern of virtualHost.domains) { + const matchType = domainPatternMatchType(domainPattern); + // If we already have a match of a better type, skip this one + if (matchType > bestMatchType) { + continue; + } + // If we already have a longer match of the same type, skip this one + if (matchType === bestMatchType && domainPattern.length <= longestMatch) { + continue; + } + if (domainMatch(matchType, domainPattern, domain)) { + targetVhost = virtualHost; + bestMatchType = matchType; + longestMatch = domainPattern.length; + } + if (bestMatchType === MatchType.EXACT_MATCH) { + break; + } + } + if (bestMatchType === MatchType.EXACT_MATCH) { + break; + } + } + return targetVhost; +} + +const numberRegex = new RE2(/^-?\d+$/u); + +function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Matcher { + let valueChecker: ValueMatcher; + switch (headerMatch.header_match_specifier) { + case 'exact_match': + valueChecker = new ExactValueMatcher(headerMatch.exact_match!); + break; + case 'safe_regex_match': + valueChecker = new SafeRegexValueMatcher(headerMatch.safe_regex_match!.regex); + break; + case 'range_match': + const start = BigInt(headerMatch.range_match!.start); + const end = BigInt(headerMatch.range_match!.end); + valueChecker = new RangeValueMatcher(start, end); + break; + case 'present_match': + valueChecker = new PresentValueMatcher(); + break; + case 'prefix_match': + valueChecker = new PrefixValueMatcher(headerMatch.prefix_match!); + break; + case 'suffix_match': + valueChecker = new SuffixValueMatcher(headerMatch.suffix_match!); + break; + default: + valueChecker = new RejectValueMatcher(); + } + return new HeaderMatcher(headerMatch.name, valueChecker, headerMatch.invert_match); +} + +function getPredicateForMatcher(routeMatch: RouteMatch__Output): Matcher { + let pathMatcher: ValueMatcher; + const caseInsensitive = routeMatch.case_sensitive?.value === false; + switch (routeMatch.path_specifier) { + case 'prefix': + pathMatcher = new PathPrefixValueMatcher(routeMatch.prefix!, caseInsensitive); + break; + case 'path': + pathMatcher = new PathExactValueMatcher(routeMatch.path!, caseInsensitive); + break; + case 'safe_regex': + pathMatcher = new PathSafeRegexValueMatcher(routeMatch.safe_regex!.regex); + break; + default: + pathMatcher = new RejectValueMatcher(); + } + const headerMatchers: Matcher[] = routeMatch.headers.map(getPredicateForHeaderMatcher); + let runtimeFraction: Fraction | null; + if (!routeMatch.runtime_fraction?.default_value) { + runtimeFraction = null; + } else { + runtimeFraction = envoyFractionToFraction(routeMatch.runtime_fraction.default_value) + } + return new FullMatcher(pathMatcher, headerMatchers, runtimeFraction); +} + +/** + * Convert a Duration protobuf message object to a Duration object as used in + * the ServiceConfig definition. The difference is that the protobuf message + * defines seconds as a long, which is represented as a string in JavaScript, + * and the one used in the service config defines it as a number. + * @param duration + */ +function protoDurationToDuration(duration: Duration__Output): Duration { + return { + seconds: Number.parseInt(duration.seconds), + nanos: duration.nanos + } +} + +function protoDurationToSecondsString(duration: Duration__Output): string { + return `${duration.seconds + duration.nanos / 1_000_000_000}s`; +} + +const DEFAULT_RETRY_BASE_INTERVAL = '0.025s' + +function getDefaultRetryMaxInterval(baseInterval: string): string { + return `${Number.parseFloat(baseInterval.substring(0, baseInterval.length - 1)) * 10}s`; +} + +const BOOTSTRAP_CONFIG_KEY = 'grpc.TEST_ONLY_DO_NOT_USE_IN_PROD.xds_bootstrap_config'; + +const RETRY_CODES: {[key: string]: status} = { + 'cancelled': status.CANCELLED, + 'deadline-exceeded': status.DEADLINE_EXCEEDED, + 'internal': status.INTERNAL, + 'resource-exhausted': status.RESOURCE_EXHAUSTED, + 'unavailable': status.UNAVAILABLE +}; + +class XdsResolver implements Resolver { + private hasReportedSuccess = false; + + private ldsWatcher: Watcher; + private rdsWatcher: Watcher + private isLdsWatcherActive = false; + /** + * The latest route config name from an LDS response. The RDS watcher is + * actively watching that name if and only if this is not null. + */ + private latestRouteConfigName: string | null = null; + + private latestRouteConfig: RouteConfiguration__Output | null = null; + + private clusterRefcounts = new Map(); + + private latestDefaultTimeout: Duration | undefined = undefined; + + private ldsHttpFilterConfigs: {name: string, config: HttpFilterConfig}[] = []; + + private xdsClient: XdsClient; + + constructor( + private target: GrpcUri, + private listener: ResolverListener, + private channelOptions: ChannelOptions + ) { + if (channelOptions[BOOTSTRAP_CONFIG_KEY]) { + const parsedConfig = JSON.parse(channelOptions[BOOTSTRAP_CONFIG_KEY]); + const validatedConfig = validateBootstrapConfig(parsedConfig); + this.xdsClient = new XdsClient(validatedConfig); + } else { + this.xdsClient = getSingletonXdsClient(); + } + this.ldsWatcher = { + onValidUpdate: (update: Listener__Output) => { + const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, update.api_listener!.api_listener!.value); + const defaultTimeout = httpConnectionManager.common_http_protocol_options?.idle_timeout; + if (defaultTimeout === null || defaultTimeout === undefined) { + this.latestDefaultTimeout = undefined; + } else { + this.latestDefaultTimeout = protoDurationToDuration(defaultTimeout); + } + if (EXPERIMENTAL_FAULT_INJECTION) { + this.ldsHttpFilterConfigs = []; + for (const filter of httpConnectionManager.http_filters) { + // typed_config must be set here, or validation would have failed + const filterConfig = parseTopLevelFilterConfig(filter.typed_config!); + if (filterConfig) { + this.ldsHttpFilterConfigs.push({name: filter.name, config: filterConfig}); + } + } + } + switch (httpConnectionManager.route_specifier) { + case 'rds': { + const routeConfigName = httpConnectionManager.rds!.route_config_name; + if (this.latestRouteConfigName !== routeConfigName) { + if (this.latestRouteConfigName !== null) { + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + } + this.xdsClient.addRouteWatcher(httpConnectionManager.rds!.route_config_name, this.rdsWatcher); + this.latestRouteConfigName = routeConfigName; + } + break; + } + case 'route_config': + if (this.latestRouteConfigName) { + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + } + this.handleRouteConfig(httpConnectionManager.route_config!); + break; + default: + // This is prevented by the validation rules + } + }, + onTransientError: (error: StatusObject) => { + /* A transient error only needs to bubble up as a failure if we have + * not already provided a ServiceConfig for the upper layer to use */ + if (!this.hasReportedSuccess) { + trace('Resolution error for target ' + uriToString(this.target) + ' due to xDS client transient error ' + error.details); + this.reportResolutionError(error.details); + } + }, + onResourceDoesNotExist: () => { + trace('Resolution error for target ' + uriToString(this.target) + ': LDS resource does not exist'); + this.reportResolutionError(`Listener ${this.target} does not exist`); + } + }; + this.rdsWatcher = { + onValidUpdate: (update: RouteConfiguration__Output) => { + this.handleRouteConfig(update); + }, + onTransientError: (error: StatusObject) => { + /* A transient error only needs to bubble up as a failure if we have + * not already provided a ServiceConfig for the upper layer to use */ + if (!this.hasReportedSuccess) { + trace('Resolution error for target ' + uriToString(this.target) + ' due to xDS client transient error ' + error.details); + this.reportResolutionError(error.details); + } + }, + onResourceDoesNotExist: () => { + trace('Resolution error for target ' + uriToString(this.target) + ' and route config ' + this.latestRouteConfigName + ': RDS resource does not exist'); + this.reportResolutionError(`Route config ${this.latestRouteConfigName} does not exist`); + } + } + } + + private refCluster(clusterName: string) { + const refCount = this.clusterRefcounts.get(clusterName); + if (refCount) { + refCount.refCount += 1; + } + } + + private unrefCluster(clusterName: string) { + const refCount = this.clusterRefcounts.get(clusterName); + if (refCount) { + refCount.refCount -= 1; + if (!refCount.inLastConfig && refCount.refCount === 0) { + this.clusterRefcounts.delete(clusterName); + this.handleRouteConfig(this.latestRouteConfig!); + } + } + } + + private handleRouteConfig(routeConfig: RouteConfiguration__Output) { + this.latestRouteConfig = routeConfig; + /* Select the virtual host using the default authority override if it + * exists, and the channel target otherwise. */ + const hostDomain = this.channelOptions['grpc.default_authority'] ?? this.target.path; + const virtualHost = findVirtualHostForDomain(routeConfig.virtual_hosts, hostDomain); + if (virtualHost === null) { + this.reportResolutionError('No matching route found for ' + hostDomain); + return; + } + const virtualHostHttpFilterOverrides = new Map(); + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const [name, filter] of Object.entries(virtualHost.typed_per_filter_config ?? {})) { + const parsedConfig = parseOverrideFilterConfig(filter); + if (parsedConfig) { + virtualHostHttpFilterOverrides.set(name, parsedConfig); + } + } + } + trace('Received virtual host config ' + JSON.stringify(virtualHost, undefined, 2)); + const allConfigClusters = new Set(); + const matchList: {matcher: Matcher, action: RouteAction}[] = []; + for (const route of virtualHost.routes) { + let routeAction: RouteAction; + let timeout: Duration | undefined; + /* For field prioritization see + * https://github.com/grpc/proposal/blob/master/A31-xds-timeout-support-and-config-selector.md#supported-fields + */ + if (route.route?.max_stream_duration?.grpc_timeout_header_max) { + timeout = protoDurationToDuration(route.route.max_stream_duration.grpc_timeout_header_max); + } else if (route.route?.max_stream_duration?.max_stream_duration) { + timeout = protoDurationToDuration(route.route.max_stream_duration.max_stream_duration); + } else { + timeout = this.latestDefaultTimeout; + } + // "A value of 0 indicates the application's deadline is used without modification." + if (timeout?.seconds === 0 && timeout.nanos === 0) { + timeout = undefined; + } + const routeHttpFilterOverrides = new Map(); + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const [name, filter] of Object.entries(route.typed_per_filter_config ?? {})) { + const parsedConfig = parseOverrideFilterConfig(filter); + if (parsedConfig) { + routeHttpFilterOverrides.set(name, parsedConfig); + } + } + } + let retryPolicy: RetryPolicy | undefined = undefined; + if (EXPERIMENTAL_RETRY) { + const retryConfig = route.route!.retry_policy ?? virtualHost.retry_policy; + if (retryConfig) { + const retryableStatusCodes = []; + for (const code of retryConfig.retry_on.split(',')) { + if (RETRY_CODES[code]) { + retryableStatusCodes.push(RETRY_CODES[code]); + } + } + if (retryableStatusCodes.length > 0) { + const baseInterval = retryConfig.retry_back_off?.base_interval ? + protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) : + DEFAULT_RETRY_BASE_INTERVAL; + const maxInterval = retryConfig.retry_back_off?.max_interval ? + protoDurationToSecondsString(retryConfig.retry_back_off.max_interval) : + getDefaultRetryMaxInterval(baseInterval); + retryPolicy = { + backoffMultiplier: 2, + initialBackoff: baseInterval, + maxBackoff: maxInterval, + maxAttempts: (retryConfig.num_retries?.value ?? 1) + 1, + retryableStatusCodes: retryableStatusCodes + }; + } + } + } + switch (route.route!.cluster_specifier) { + case 'cluster_header': + continue; + case 'cluster':{ + const cluster = route.route!.cluster!; + allConfigClusters.add(cluster); + const extraFilterFactories: FilterFactory[] = []; + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const filterConfig of this.ldsHttpFilterConfigs) { + if (routeHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else if (virtualHostHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else { + const filter = createHttpFilter(filterConfig.config); + if (filter) { + extraFilterFactories.push(filter); + } + } + } + } + routeAction = new SingleClusterRouteAction(cluster, {name: [], timeout: timeout, retryPolicy: retryPolicy}, extraFilterFactories); + break; + } + case 'weighted_clusters': { + const weightedClusters: WeightedCluster[] = []; + for (const clusterWeight of route.route!.weighted_clusters!.clusters) { + allConfigClusters.add(clusterWeight.name); + const extraFilterFactories: FilterFactory[] = []; + const clusterHttpFilterOverrides = new Map(); + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const [name, filter] of Object.entries(clusterWeight.typed_per_filter_config ?? {})) { + const parsedConfig = parseOverrideFilterConfig(filter); + if (parsedConfig) { + clusterHttpFilterOverrides.set(name, parsedConfig); + } + } + for (const filterConfig of this.ldsHttpFilterConfigs) { + if (clusterHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, clusterHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else if (routeHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else if (virtualHostHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else { + const filter = createHttpFilter(filterConfig.config); + if (filter) { + extraFilterFactories.push(filter); + } + } + } + } + weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, dynamicFilterFactories: extraFilterFactories}); + } + routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, {name: [], timeout: timeout, retryPolicy: retryPolicy}); + break; + } + default: + /* The validation logic should prevent us from reaching this point. + * This is just for the type checker. */ + continue; + } + const routeMatcher = getPredicateForMatcher(route.match!); + matchList.push({matcher: routeMatcher, action: routeAction}); + } + /* Mark clusters that are not in this route config, and remove ones with + * no references */ + for (const [name, refCount] of Array.from(this.clusterRefcounts.entries())) { + if (!allConfigClusters.has(name)) { + refCount.inLastConfig = false; + if (refCount.refCount === 0) { + this.clusterRefcounts.delete(name); + } + } + } + // Add any new clusters from this route config + for (const name of allConfigClusters) { + if (this.clusterRefcounts.has(name)) { + this.clusterRefcounts.get(name)!.inLastConfig = true; + } else { + this.clusterRefcounts.set(name, {inLastConfig: true, refCount: 0}); + } + } + const configSelector: ConfigSelector = (methodName, metadata) => { + for (const {matcher, action} of matchList) { + if (matcher.apply(methodName, metadata)) { + const clusterResult = action.getCluster(); + this.refCluster(clusterResult.name); + const onCommitted = () => { + this.unrefCluster(clusterResult.name); + } + return { + methodConfig: clusterResult.methodConfig, + onCommitted: onCommitted, + pickInformation: {cluster: clusterResult.name}, + status: status.OK, + dynamicFilterFactories: clusterResult.dynamicFilterFactories + }; + } + } + return { + methodConfig: {name: []}, + // cluster won't be used here, but it's set because of some TypeScript weirdness + pickInformation: {cluster: ''}, + status: status.UNAVAILABLE, + dynamicFilterFactories: [] + }; + }; + trace('Created ConfigSelector with configuration:'); + for (const {matcher, action} of matchList) { + trace(matcher.toString()); + trace('=> ' + action.toString()); + } + const clusterConfigMap = new Map(); + for (const clusterName of this.clusterRefcounts.keys()) { + clusterConfigMap.set(clusterName, {child_policy: [new CdsLoadBalancingConfig(clusterName)]}); + } + const lbPolicyConfig = new XdsClusterManagerLoadBalancingConfig(clusterConfigMap); + const serviceConfig: ServiceConfig = { + methodConfig: [], + loadBalancingConfig: [lbPolicyConfig] + } + this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {xdsClient: this.xdsClient}); + } + + private reportResolutionError(reason: string) { + this.listener.onError({ + code: status.UNAVAILABLE, + details: `xDS name resolution failed for target ${uriToString( + this.target + )}: ${reason}`, + metadata: new Metadata(), + }); + } + + updateResolution(): void { + // Wait until updateResolution is called once to start the xDS requests + if (!this.isLdsWatcherActive) { + trace('Starting resolution for target ' + uriToString(this.target)); + this.xdsClient.addListenerWatcher(this.target.path, this.ldsWatcher); + this.isLdsWatcherActive = true; + } + } + + destroy() { + this.xdsClient.removeListenerWatcher(this.target.path, this.ldsWatcher); + if (this.latestRouteConfigName) { + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + } + } + + static getDefaultAuthority(target: GrpcUri) { + return target.path; + } +} + +export function setup() { + registerResolver('xds', XdsResolver); +} diff --git a/packages/grpc-js-xds/src/resources.ts b/packages/grpc-js-xds/src/resources.ts new file mode 100644 index 000000000..0972ce97d --- /dev/null +++ b/packages/grpc-js-xds/src/resources.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2021 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. + * + */ + +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; +import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; +import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; +import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; +import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; + +export const EDS_TYPE_URL = 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; +export const CDS_TYPE_URL = 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; +export const LDS_TYPE_URL = 'type.googleapis.com/envoy.config.listener.v3.Listener'; +export const RDS_TYPE_URL = 'type.googleapis.com/envoy.config.route.v3.RouteConfiguration'; + +export type EdsTypeUrl = 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; +export type CdsTypeUrl = 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; +export type LdsTypeUrl = 'type.googleapis.com/envoy.config.listener.v3.Listener'; +export type RdsTypeUrl = 'type.googleapis.com/envoy.config.route.v3.RouteConfiguration'; + +export type AdsTypeUrl = EdsTypeUrl | CdsTypeUrl | RdsTypeUrl | LdsTypeUrl; + +export const HTTP_CONNECTION_MANGER_TYPE_URL = + 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager'; + +export type HttpConnectionManagerTypeUrl = 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager'; + +/** + * Map type URLs to their corresponding message types + */ +export type AdsOutputType = T extends EdsTypeUrl + ? ClusterLoadAssignment__Output + : T extends CdsTypeUrl + ? Cluster__Output + : T extends RdsTypeUrl + ? RouteConfiguration__Output + : T extends LdsTypeUrl + ? Listener__Output + : HttpConnectionManager__Output; + +const resourceRoot = loadProtosWithOptionsSync([ + 'envoy/config/listener/v3/listener.proto', + 'envoy/config/route/v3/route.proto', + 'envoy/config/cluster/v3/cluster.proto', + 'envoy/config/endpoint/v3/endpoint.proto', + 'envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/googleapis/', + __dirname + '/../../deps/protoc-gen-validate/', + ], + } +); + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +export function decodeSingleResource(targetTypeUrl: T, message: Buffer): AdsOutputType { + const name = targetTypeUrl.substring(targetTypeUrl.lastIndexOf('/') + 1); + const type = resourceRoot.lookup(name); + if (type) { + const decodedMessage = (type as any).decode(message); + return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as AdsOutputType; + } else { + throw new Error(`ADS Error: unknown resource type ${targetTypeUrl}`); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/route-action.ts b/packages/grpc-js-xds/src/route-action.ts new file mode 100644 index 000000000..5ae5885af --- /dev/null +++ b/packages/grpc-js-xds/src/route-action.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2021 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. + */ + +import { experimental } from '@grpc/grpc-js'; +import Duration = experimental.Duration; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import MethodConfig = experimental.MethodConfig; + +export interface ClusterResult { + name: string; + methodConfig: MethodConfig; + dynamicFilterFactories: FilterFactory[]; +} + +export interface RouteAction { + toString(): string; + getCluster(): ClusterResult; +} + +function durationToLogString(duration: Duration) { + const millis = Math.floor(duration.nanos / 1_000_000); + if (millis > 0) { + return duration.seconds + '.' + millis; + } else { + return '' + duration.seconds; + } +} + +export class SingleClusterRouteAction implements RouteAction { + constructor(private cluster: string, private methodConfig: MethodConfig, private extraFilterFactories: FilterFactory[]) {} + + getCluster() { + return { + name: this.cluster, + methodConfig: this.methodConfig, + dynamicFilterFactories: this.extraFilterFactories + }; + } + + toString() { + return 'SingleCluster(' + this.cluster + ', ' + JSON.stringify(this.methodConfig) + ')'; + } +} + +export interface WeightedCluster { + name: string; + weight: number; + dynamicFilterFactories: FilterFactory[]; +} + +interface ClusterChoice { + name: string; + numerator: number; + dynamicFilterFactories: FilterFactory[]; +} + +export class WeightedClusterRouteAction implements RouteAction { + /** + * The weighted cluster choices represented as a CDF + */ + private clusterChoices: ClusterChoice[]; + constructor(private clusters: WeightedCluster[], private totalWeight: number, private methodConfig: MethodConfig) { + this.clusterChoices = []; + let lastNumerator = 0; + for (const clusterWeight of clusters) { + lastNumerator += clusterWeight.weight; + this.clusterChoices.push({name: clusterWeight.name, numerator: lastNumerator, dynamicFilterFactories: clusterWeight.dynamicFilterFactories}); + } + } + + getCluster() { + const randomNumber = Math.random() * this.totalWeight; + for (const choice of this.clusterChoices) { + if (randomNumber < choice.numerator) { + return { + name: choice.name, + methodConfig: this.methodConfig, + dynamicFilterFactories: choice.dynamicFilterFactories + }; + } + } + // This should be prevented by the validation rules + return {name: '', methodConfig: this.methodConfig, dynamicFilterFactories: []}; + } + + toString() { + const clusterListString = this.clusters.map(({name, weight}) => '(' + name + ':' + weight + ')').join(', ') + return 'WeightedCluster(' + clusterListString + ', ' + JSON.stringify(this.methodConfig) + ')'; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts new file mode 100644 index 000000000..72a0ca375 --- /dev/null +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -0,0 +1,309 @@ +/* + * Copyright 2020 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. + * + */ + +import * as fs from 'fs'; +import { Struct } from './generated/google/protobuf/Struct'; +import { Value } from './generated/google/protobuf/Value'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export interface Locality { + region?: string; + zone?: string; + sub_zone?: string; +} + +export interface Node { + id: string, + locality: Locality; + cluster?: string; + metadata?: Struct; +} + +export interface ChannelCredsConfig { + type: string; + config?: object; +} + +export interface XdsServerConfig { + serverUri: string; + channelCreds: ChannelCredsConfig[]; + serverFeatures: string[]; +} + +export interface BootstrapInfo { + xdsServers: XdsServerConfig[]; + node: Node; +} + +function validateChannelCredsConfig(obj: any): ChannelCredsConfig { + if (!('type' in obj)) { + throw new Error('type field missing in xds_servers.channel_creds element'); + } + if (typeof obj.type !== 'string') { + throw new Error( + `xds_servers.channel_creds.type field: expected string, got ${typeof obj.type}` + ); + } + if ('config' in obj) { + if (typeof obj.config !== 'object' || obj.config === null) { + throw new Error( + 'xds_servers.channel_creds config field must be an object if provided' + ); + } + } + return { + type: obj.type, + config: obj.config, + }; +} + +function validateXdsServerConfig(obj: any): XdsServerConfig { + if (!('server_uri' in obj)) { + throw new Error('server_uri field missing in xds_servers element'); + } + if (typeof obj.server_uri !== 'string') { + throw new Error( + `xds_servers.server_uri field: expected string, got ${typeof obj.server_uri}` + ); + } + if (!('channel_creds' in obj)) { + throw new Error('channel_creds missing in xds_servers element'); + } + if (!Array.isArray(obj.channel_creds)) { + throw new Error( + `xds_servers.channel_creds field: expected array, got ${typeof obj.channel_creds}` + ); + } + if (obj.channel_creds.length === 0) { + throw new Error( + 'xds_servers.channel_creds field: at least one entry is required' + ); + } + if ('server_features' in obj) { + if (!Array.isArray(obj.server_features)) { + throw new Error( + `xds_servers.server_features field: expected array, got ${typeof obj.server_features}` + ); + } + for (const feature of obj.server_features) { + if (typeof feature !== 'string') { + `xds_servers.server_features field element: expected string, got ${typeof feature}` + } + } + } + return { + serverUri: obj.server_uri, + channelCreds: obj.channel_creds.map(validateChannelCredsConfig), + serverFeatures: obj.server_features ?? [] + }; +} + +function validateValue(obj: any): Value { + if (Array.isArray(obj)) { + return { + kind: 'listValue', + listValue: { + values: obj.map((value) => validateValue(value)), + }, + }; + } else { + switch (typeof obj) { + case 'boolean': + return { + kind: 'boolValue', + boolValue: obj, + }; + case 'number': + return { + kind: 'numberValue', + numberValue: obj, + }; + case 'string': + return { + kind: 'stringValue', + stringValue: obj, + }; + case 'object': + if (obj === null) { + return { + kind: 'nullValue', + nullValue: 'NULL_VALUE', + }; + } else { + return { + kind: 'structValue', + structValue: getStructFromJson(obj), + }; + } + default: + throw new Error(`Could not handle struct value of type ${typeof obj}`); + } + } +} + +function getStructFromJson(obj: any): Struct { + if (typeof obj !== 'object' || obj === null) { + throw new Error('Invalid JSON object for Struct field'); + } + const fields: { [key: string]: Value } = {}; + for (const [fieldName, value] of Object.entries(obj)) { + fields[fieldName] = validateValue(value); + } + return { + fields, + }; +} + +/** + * Validate that the input obj is a valid Node proto message. Only checks the + * fields we expect to see: id, cluster, locality, and metadata. + * @param obj + */ +function validateNode(obj: any): Node { + const result: Node = { + id: '', + locality: {} + }; + if (!('id' in obj)) { + throw new Error('id field missing in node element'); + } + if (typeof obj.id !== 'string') { + throw new Error(`node.id field: expected string, got ${typeof obj.id}`); + } + result.id = obj.id; + if (!('locality' in obj)) { + throw new Error('locality field missing in node element'); + } + result.locality = {}; + if ('region' in obj.locality) { + if (typeof obj.locality.region !== 'string') { + throw new Error( + `node.locality.region field: expected string, got ${typeof obj.locality + .region}` + ); + } + result.locality.region = obj.locality.region; + } + if ('zone' in obj.locality) { + if (typeof obj.locality.zone !== 'string') { + throw new Error( + `node.locality.zone field: expected string, got ${typeof obj.locality + .zone}` + ); + } + result.locality.zone = obj.locality.zone; + } + if ('sub_zone' in obj.locality) { + if (typeof obj.locality.sub_zone !== 'string') { + throw new Error( + `node.locality.sub_zone field: expected string, got ${typeof obj + .locality.sub_zone}` + ); + } + result.locality.sub_zone = obj.locality.sub_zone; + } + if ('cluster' in obj) { + if (typeof obj.cluster !== 'string') { + throw new Error( + `node.cluster field: expected string, got ${typeof obj.cluster}` + ); + } + result.cluster = obj.cluster; + } + if ('metadata' in obj) { + result.metadata = getStructFromJson(obj.metadata); + } + return result; +} + +export function validateBootstrapConfig(obj: any): BootstrapInfo { + return { + xdsServers: obj.xds_servers.map(validateXdsServerConfig), + node: validateNode(obj.node), + }; +} + +let loadedBootstrapInfo: Promise | null = null; + +export async function loadBootstrapInfo(): Promise { + if (loadedBootstrapInfo !== null) { + return loadedBootstrapInfo; + } + + /** + * If GRPC_XDS_BOOTSTRAP exists + * then use its value as the name of the bootstrap file. + * + * If the file is missing or the contents of the file are malformed, + * return an error. + */ + const bootstrapPath = process.env.GRPC_XDS_BOOTSTRAP; + if (bootstrapPath) { + loadedBootstrapInfo = new Promise((resolve, reject) => { + fs.readFile(bootstrapPath, { encoding: 'utf8' }, (err, data) => { + if (err) { + reject( + new Error( + `Failed to read xDS bootstrap file from path ${bootstrapPath} with error ${err.message}` + ) + ); + } + try { + const parsedFile = JSON.parse(data); + resolve(validateBootstrapConfig(parsedFile)); + } catch (e) { + reject( + new Error( + `Failed to parse xDS bootstrap file at path ${bootstrapPath} with error ${e.message}` + ) + ); + } + }); + }); + return loadedBootstrapInfo; + } + + /** + * Else, if GRPC_XDS_BOOTSTRAP_CONFIG exists + * then use its value as the bootstrap config. + * + * If the value is malformed, return an error. + * + * See: https://github.com/grpc/grpc-node/issues/1868 + */ + const bootstrapConfig = process.env.GRPC_XDS_BOOTSTRAP_CONFIG; + if (bootstrapConfig) { + try { + const parsedConfig = JSON.parse(bootstrapConfig); + const loadedBootstrapInfoValue = validateBootstrapConfig(parsedConfig); + loadedBootstrapInfo = Promise.resolve(loadedBootstrapInfoValue); + } catch (e) { + throw new Error( + `Failed to parse xDS bootstrap config from environment variable GRPC_XDS_BOOTSTRAP_CONFIG with error ${e.message}` + ); + } + + return loadedBootstrapInfo; + } + + return Promise.reject( + new Error( + 'The GRPC_XDS_BOOTSTRAP or GRPC_XDS_BOOTSTRAP_CONFIG environment variables need to be set to the path to the bootstrap file to use xDS' + ) + ); +} diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts new file mode 100644 index 000000000..20c914a1c --- /dev/null +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -0,0 +1,1001 @@ +/* + * Copyright 2020 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. + * + */ + +import * as protoLoader from '@grpc/proto-loader'; +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel, connectivityState } from '@grpc/grpc-js'; +import * as adsTypes from './generated/ads'; +import * as lrsTypes from './generated/lrs'; +import { BootstrapInfo, loadBootstrapInfo } from './xds-bootstrap'; +import { Node } from './generated/envoy/config/core/v3/Node'; +import { AggregatedDiscoveryServiceClient } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService'; +import { DiscoveryRequest } from './generated/envoy/service/discovery/v3/DiscoveryRequest'; +import { DiscoveryResponse__Output } from './generated/envoy/service/discovery/v3/DiscoveryResponse'; +import { LoadReportingServiceClient } from './generated/envoy/service/load_stats/v3/LoadReportingService'; +import { LoadStatsRequest } from './generated/envoy/service/load_stats/v3/LoadStatsRequest'; +import { LoadStatsResponse__Output } from './generated/envoy/service/load_stats/v3/LoadStatsResponse'; +import { Locality, Locality__Output } from './generated/envoy/config/core/v3/Locality'; +import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; +import { Any__Output } from './generated/google/protobuf/Any'; +import BackoffTimeout = experimental.BackoffTimeout; +import ServiceConfig = experimental.ServiceConfig; +import { createGoogleDefaultCredentials } from './google-default-credentials'; +import { CdsLoadBalancingConfig } from './load-balancer-cds'; +import { EdsState } from './xds-stream-state/eds-state'; +import { CdsState } from './xds-stream-state/cds-state'; +import { RdsState } from './xds-stream-state/rds-state'; +import { LdsState } from './xds-stream-state/lds-state'; +import { HandleResponseResult, ResourcePair, Watcher } from './xds-stream-state/xds-stream-state'; +import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; +import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; +import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; +import { Duration } from './generated/google/protobuf/Duration'; +import { AdsOutputType, AdsTypeUrl, CDS_TYPE_URL, decodeSingleResource, EDS_TYPE_URL, LDS_TYPE_URL, RDS_TYPE_URL } from './resources'; +import { setCsdsClientNode, updateCsdsRequestedNameList, updateCsdsResourceResponse } from './csds'; + +const TRACER_NAME = 'xds_client'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const clientVersion = require('../../package.json').version; + +let loadedProtos: Promise< + adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType +> | null = null; + +function loadAdsProtos(): Promise< + adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType +> { + if (loadedProtos !== null) { + return loadedProtos; + } + loadedProtos = protoLoader + .load( + [ + 'envoy/service/discovery/v3/ads.proto', + 'envoy/service/load_stats/v3/lrs.proto', + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/googleapis/', + __dirname + '/../../deps/protoc-gen-validate/', + ], + } + ) + .then( + (packageDefinition) => + (loadPackageDefinition( + packageDefinition + ) as unknown) as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType + ); + return loadedProtos; +} + +function localityEqual( + loc1: Locality__Output, + loc2: Locality__Output +): boolean { + return ( + loc1.region === loc2.region && + loc1.zone === loc2.zone && + loc1.sub_zone === loc2.sub_zone + ); +} + +export interface XdsClusterDropStats { + addUncategorizedCallDropped(): void; + addCallDropped(category: string): void; +} + +export interface XdsClusterLocalityStats { + addCallStarted(): void; + addCallFinished(fail: boolean): void; +} + +interface DroppedRequests { + category: string; + dropped_count: number; +} + +interface UpstreamLocalityStats { + locality: Locality; + total_issued_requests: number; + total_successful_requests: number; + total_error_requests: number; + total_requests_in_progress: number; +} + +/** + * An interface representing the ClusterStats message type, restricted to the + * fields used in this module to ensure compatibility with both v2 and v3 APIs. + */ +interface ClusterStats { + cluster_name: string; + cluster_service_name: string; + dropped_requests: DroppedRequests[]; + total_dropped_requests: number; + upstream_locality_stats: UpstreamLocalityStats[]; + load_report_interval: Duration +} + +interface ClusterLocalityStats { + locality: Locality__Output; + callsStarted: number; + callsSucceeded: number; + callsFailed: number; + callsInProgress: number; +} + +interface ClusterLoadReport { + callsDropped: Map; + uncategorizedCallsDropped: number; + localityStats: ClusterLocalityStats[]; + intervalStart: [number, number]; +} + +class ClusterLoadReportMap { + private statsMap: { + clusterName: string; + edsServiceName: string; + stats: ClusterLoadReport; + }[] = []; + + get( + clusterName: string, + edsServiceName: string + ): ClusterLoadReport | undefined { + for (const statsObj of this.statsMap) { + if ( + statsObj.clusterName === clusterName && + statsObj.edsServiceName === edsServiceName + ) { + return statsObj.stats; + } + } + return undefined; + } + + getOrCreate(clusterName: string, edsServiceName: string): ClusterLoadReport { + for (const statsObj of this.statsMap) { + if ( + statsObj.clusterName === clusterName && + statsObj.edsServiceName === edsServiceName + ) { + return statsObj.stats; + } + } + const newStats: ClusterLoadReport = { + callsDropped: new Map(), + uncategorizedCallsDropped: 0, + localityStats: [], + intervalStart: process.hrtime(), + }; + this.statsMap.push({ + clusterName, + edsServiceName, + stats: newStats, + }); + return newStats; + } + + *entries(): IterableIterator< + [{ clusterName: string; edsServiceName: string }, ClusterLoadReport] + > { + for (const statsEntry of this.statsMap) { + yield [ + { + clusterName: statsEntry.clusterName, + edsServiceName: statsEntry.edsServiceName, + }, + statsEntry.stats, + ]; + } + } +} + +type AdsServiceKind = 'eds' | 'cds' | 'rds' | 'lds'; + +interface AdsState { + eds: EdsState; + cds: CdsState; + rds: RdsState; + lds: LdsState; +} + +function getResponseMessages( + targetTypeUrl: T, + resources: Any__Output[] +): ResourcePair>[] { + const result: ResourcePair>[] = []; + for (const resource of resources) { + if (resource.type_url !== targetTypeUrl) { + throw new Error( + `ADS Error: Invalid resource type ${resource.type_url}, expected ${targetTypeUrl}` + ); + } + result.push({ + resource: decodeSingleResource(targetTypeUrl, resource.value), + raw: resource + }); + } + return result; +} + +export class XdsClient { + + private adsNode: Node | null = null; + private adsClient: AggregatedDiscoveryServiceClient | null = null; + private adsCall: ClientDuplexStream< + DiscoveryRequest, + DiscoveryResponse__Output + > | null = null; + private receivedAdsResponseOnCurrentStream = false; + + private lrsNode: Node | null = null; + private lrsClient: LoadReportingServiceClient | null = null; + private lrsCall: ClientDuplexStream< + LoadStatsRequest, + LoadStatsResponse__Output + > | null = null; + private latestLrsSettings: LoadStatsResponse__Output | null = null; + private receivedLrsSettingsForCurrentStream = false; + + private clusterStatsMap: ClusterLoadReportMap = new ClusterLoadReportMap(); + private statsTimer: NodeJS.Timer; + + private hasShutdown = false; + + private adsState: AdsState; + + private adsBackoff: BackoffTimeout; + private lrsBackoff: BackoffTimeout; + + constructor(bootstrapInfoOverride?: BootstrapInfo) { + const edsState = new EdsState(() => { + this.updateNames('eds'); + }); + const cdsState = new CdsState(() => { + this.updateNames('cds'); + }); + const rdsState = new RdsState(() => { + this.updateNames('rds'); + }); + const ldsState = new LdsState(rdsState, () => { + this.updateNames('lds'); + }); + this.adsState = { + eds: edsState, + cds: cdsState, + rds: rdsState, + lds: ldsState, + }; + + const channelArgs = { + // 5 minutes + 'grpc.keepalive_time_ms': 5 * 60 * 1000 + } + + this.adsBackoff = new BackoffTimeout(() => { + this.maybeStartAdsStream(); + }); + this.adsBackoff.unref(); + this.lrsBackoff = new BackoffTimeout(() => { + this.maybeStartLrsStream(); + }); + this.lrsBackoff.unref(); + + async function getBootstrapInfo(): Promise { + if (bootstrapInfoOverride) { + return bootstrapInfoOverride; + } else { + return loadBootstrapInfo(); + } + } + + Promise.all([getBootstrapInfo(), loadAdsProtos()]).then( + ([bootstrapInfo, protoDefinitions]) => { + if (this.hasShutdown) { + return; + } + trace('Loaded bootstrap info: ' + JSON.stringify(bootstrapInfo, undefined, 2)); + if (bootstrapInfo.xdsServers.length < 1) { + trace('Failed to initialize xDS Client. No servers provided in bootstrap info.'); + // Bubble this error up to any listeners + this.reportStreamError({ + code: status.INTERNAL, + details: 'Failed to initialize xDS Client. No servers provided in bootstrap info.', + metadata: new Metadata(), + }); + return; + } + if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('ignore_resource_deletion') >= 0) { + this.adsState.lds.enableIgnoreResourceDeletion(); + this.adsState.cds.enableIgnoreResourceDeletion(); + } + const userAgentName = 'gRPC Node Pure JS'; + this.adsNode = { + ...bootstrapInfo.node, + user_agent_name: userAgentName, + user_agent_version: clientVersion, + client_features: ['envoy.lb.does_not_support_overprovisioning'], + }; + this.lrsNode = { + ...bootstrapInfo.node, + user_agent_name: userAgentName, + user_agent_version: clientVersion, + client_features: ['envoy.lrs.supports_send_all_clusters'], + }; + setCsdsClientNode(this.adsNode); + trace('ADS Node: ' + JSON.stringify(this.adsNode, undefined, 2)); + trace('LRS Node: ' + JSON.stringify(this.lrsNode, undefined, 2)); + const credentialsConfigs = bootstrapInfo.xdsServers[0].channelCreds; + let channelCreds: ChannelCredentials | null = null; + for (const config of credentialsConfigs) { + if (config.type === 'google_default') { + channelCreds = createGoogleDefaultCredentials(); + break; + } else if (config.type === 'insecure') { + channelCreds = ChannelCredentials.createInsecure(); + break; + } + } + if (channelCreds === null) { + trace('Failed to initialize xDS Client. No valid credentials types found.'); + // Bubble this error up to any listeners + this.reportStreamError({ + code: status.INTERNAL, + details: 'Failed to initialize xDS Client. No valid credentials types found.', + metadata: new Metadata(), + }); + return; + } + const serverUri = bootstrapInfo.xdsServers[0].serverUri + trace('Starting xDS client connected to server URI ' + bootstrapInfo.xdsServers[0].serverUri); + const channel = new Channel(serverUri, channelCreds, channelArgs); + this.adsClient = new protoDefinitions.envoy.service.discovery.v3.AggregatedDiscoveryService( + serverUri, + channelCreds, + {channelOverride: channel} + ); + this.maybeStartAdsStream(); + channel.watchConnectivityState(channel.getConnectivityState(false), Infinity, () => { + this.handleAdsConnectivityStateUpdate(); + }) + + this.lrsClient = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( + serverUri, + channelCreds, + {channelOverride: channel} + ); + this.maybeStartLrsStream(); + }).catch((error) => { + trace('Failed to initialize xDS Client. ' + error.message); + // Bubble this error up to any listeners + this.reportStreamError({ + code: status.INTERNAL, + details: `Failed to initialize xDS Client. ${error.message}`, + metadata: new Metadata(), + }); + } + ); + this.statsTimer = setInterval(() => {}, 0); + clearInterval(this.statsTimer); + } + + private handleAdsConnectivityStateUpdate() { + if (!this.adsClient) { + return; + } + const state = this.adsClient.getChannel().getConnectivityState(false); + if (state === connectivityState.READY && this.adsCall) { + this.reportAdsStreamStarted(); + } + if (state === connectivityState.TRANSIENT_FAILURE) { + this.reportStreamError({ + code: status.UNAVAILABLE, + details: 'No connection established to xDS server', + metadata: new Metadata() + }); + } + this.adsClient.getChannel().watchConnectivityState(state, Infinity, () => { + this.handleAdsConnectivityStateUpdate(); + }); + } + + private handleAdsResponse(message: DiscoveryResponse__Output) { + this.receivedAdsResponseOnCurrentStream = true; + this.adsBackoff.reset(); + let handleResponseResult: { + result: HandleResponseResult; + serviceKind: AdsServiceKind; + } | null = null; + try { + switch (message.type_url) { + case EDS_TYPE_URL: + handleResponseResult = { + result: this.adsState.eds.handleResponses( + getResponseMessages(EDS_TYPE_URL, message.resources) + ), + serviceKind: 'eds' + }; + break; + case CDS_TYPE_URL: + handleResponseResult = { + result: this.adsState.cds.handleResponses( + getResponseMessages(CDS_TYPE_URL, message.resources) + ), + serviceKind: 'cds' + }; + break; + case RDS_TYPE_URL: + handleResponseResult = { + result: this.adsState.rds.handleResponses( + getResponseMessages(RDS_TYPE_URL, message.resources) + ), + serviceKind: 'rds' + }; + break; + case LDS_TYPE_URL: + handleResponseResult = { + result: this.adsState.lds.handleResponses( + getResponseMessages(LDS_TYPE_URL, message.resources) + ), + serviceKind: 'lds' + } + break; + } + } catch (e) { + trace('Nacking message with protobuf parsing error: ' + e.message); + this.nack(message.type_url, e.message); + return; + } + if (handleResponseResult === null) { + // Null handleResponseResult means that the type_url was unrecognized + trace('Nacking message with unknown type URL ' + message.type_url); + this.nack(message.type_url, `Unknown type_url ${message.type_url}`); + } else { + updateCsdsResourceResponse(message.type_url as AdsTypeUrl, message.version_info, handleResponseResult.result); + if (handleResponseResult.result.rejected.length > 0) { + // rejected.length > 0 means that at least one message validation failed + const errorString = `${handleResponseResult.serviceKind.toUpperCase()} Error: ${handleResponseResult.result.rejected[0].error}`; + trace('Nacking message with type URL ' + message.type_url + ': ' + errorString); + this.nack(message.type_url, errorString); + } else { + // If we get here, all message validation succeeded + trace('Acking message with type URL ' + message.type_url); + const serviceKind = handleResponseResult.serviceKind; + this.adsState[serviceKind].nonce = message.nonce; + this.adsState[serviceKind].versionInfo = message.version_info; + this.ack(serviceKind); + } + } + } + + private handleAdsCallStatus(streamStatus: StatusObject) { + trace( + 'ADS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details + ); + this.adsCall = null; + if (streamStatus.code !== status.OK && !this.receivedAdsResponseOnCurrentStream) { + this.reportStreamError(streamStatus); + } + /* If the backoff timer is no longer running, we do not need to wait any + * more to start the new call. */ + if (!this.adsBackoff.isRunning()) { + this.maybeStartAdsStream(); + } + } + + /** + * Start the ADS stream if the client exists and there is not already an + * existing stream, and there are resources to request. + */ + private maybeStartAdsStream() { + if (this.hasShutdown) { + return; + } + if (this.adsState.eds.getResourceNames().length === 0 && + this.adsState.cds.getResourceNames().length === 0 && + this.adsState.rds.getResourceNames().length === 0 && + this.adsState.lds.getResourceNames().length === 0) { + return; + } + if (this.adsClient === null) { + return; + } + if (this.adsCall !== null) { + return; + } + this.receivedAdsResponseOnCurrentStream = false; + const metadata = new Metadata({waitForReady: true}); + this.adsCall = this.adsClient.StreamAggregatedResources(metadata); + this.adsCall.on('data', (message: DiscoveryResponse__Output) => { + this.handleAdsResponse(message); + }); + this.adsCall.on('status', (status: StatusObject) => { + this.handleAdsCallStatus(status); + }); + this.adsCall.on('error', () => {}); + trace('Started ADS stream'); + // Backoff relative to when we start the request + this.adsBackoff.runOnce(); + + const allServiceKinds: AdsServiceKind[] = ['eds', 'cds', 'rds', 'lds']; + for (const service of allServiceKinds) { + const state = this.adsState[service]; + if (state.getResourceNames().length > 0) { + this.updateNames(service); + } + } + if (this.adsClient.getChannel().getConnectivityState(false) === connectivityState.READY) { + this.reportAdsStreamStarted(); + } + } + + private maybeSendAdsMessage(typeUrl: string, resourceNames: string[], responseNonce: string, versionInfo: string, errorMessage?: string) { + this.adsCall?.write({ + node: this.adsNode!, + type_url: typeUrl, + resource_names: resourceNames, + response_nonce: responseNonce, + version_info: versionInfo, + error_detail: errorMessage ? { message: errorMessage } : undefined + }); + } + + private getTypeUrl(serviceKind: AdsServiceKind): AdsTypeUrl { + switch (serviceKind) { + case 'eds': + return EDS_TYPE_URL; + case 'cds': + return CDS_TYPE_URL; + case 'rds': + return RDS_TYPE_URL; + case 'lds': + return LDS_TYPE_URL; + } + } + + /** + * Acknowledge an update. This should be called after the local nonce and + * version info are updated so that it sends the post-update values. + */ + ack(serviceKind: AdsServiceKind) { + this.updateNames(serviceKind); + } + + /** + * Reject an update. This should be called without updating the local + * nonce and version info. + */ + private nack(typeUrl: string, message: string) { + let resourceNames: string[]; + let nonce: string; + let versionInfo: string; + let serviceKind: AdsServiceKind | null; + switch (typeUrl) { + case EDS_TYPE_URL: + serviceKind = 'eds'; + break; + case CDS_TYPE_URL: + serviceKind = 'cds'; + break; + case RDS_TYPE_URL: + serviceKind = 'rds'; + break; + case LDS_TYPE_URL: + serviceKind = 'lds'; + break; + default: + serviceKind = null; + break; + } + if (serviceKind) { + this.adsState[serviceKind].reportStreamError({ + code: status.UNAVAILABLE, + details: message + ' Node ID=' + this.adsNode!.id, + metadata: new Metadata() + }); + resourceNames = this.adsState[serviceKind].getResourceNames(); + nonce = this.adsState[serviceKind].nonce; + versionInfo = this.adsState[serviceKind].versionInfo; + } else { + resourceNames = []; + nonce = ''; + versionInfo = ''; + } + this.maybeSendAdsMessage(typeUrl, resourceNames, nonce, versionInfo, message); + } + + private updateNames(serviceKind: AdsServiceKind) { + if (this.adsState.eds.getResourceNames().length === 0 && + this.adsState.cds.getResourceNames().length === 0 && + this.adsState.rds.getResourceNames().length === 0 && + this.adsState.lds.getResourceNames().length === 0) { + this.adsCall?.end(); + this.adsCall = null; + this.lrsCall?.end(); + this.lrsCall = null; + return; + } + this.maybeStartAdsStream(); + this.maybeStartLrsStream(); + if (!this.adsCall) { + /* If the stream is not set up yet at this point, shortcut the rest + * becuase nothing will actually be sent. This would mainly happen if + * the bootstrap file has not been read yet. In that case, the output + * of getTypeUrl is garbage and everything after that is invalid. */ + return; + } + trace('Sending update for ' + serviceKind + ' with names ' + this.adsState[serviceKind].getResourceNames()); + const typeUrl = this.getTypeUrl(serviceKind); + updateCsdsRequestedNameList(typeUrl, this.adsState[serviceKind].getResourceNames()); + this.maybeSendAdsMessage(typeUrl, this.adsState[serviceKind].getResourceNames(), this.adsState[serviceKind].nonce, this.adsState[serviceKind].versionInfo); + } + + private reportStreamError(status: StatusObject) { + status = {...status, details: status.details + ' Node ID=' + this.adsNode!.id}; + this.adsState.eds.reportStreamError(status); + this.adsState.cds.reportStreamError(status); + this.adsState.rds.reportStreamError(status); + this.adsState.lds.reportStreamError(status); + } + + private reportAdsStreamStarted() { + this.adsState.eds.reportAdsStreamStart(); + this.adsState.cds.reportAdsStreamStart(); + this.adsState.rds.reportAdsStreamStart(); + this.adsState.lds.reportAdsStreamStart(); + } + + private handleLrsResponse(message: LoadStatsResponse__Output) { + trace('Received LRS response'); + /* Once we get any response from the server, we assume that the stream is + * in a good state, so we can reset the backoff timer. */ + this.lrsBackoff.reset(); + if ( + !this.receivedLrsSettingsForCurrentStream || + message.load_reporting_interval?.seconds !== + this.latestLrsSettings?.load_reporting_interval?.seconds || + message.load_reporting_interval?.nanos !== + this.latestLrsSettings?.load_reporting_interval?.nanos + ) { + /* Only reset the timer if the interval has changed or was not set + * before. */ + clearInterval(this.statsTimer); + /* Convert a google.protobuf.Duration to a number of milliseconds for + * use with setInterval. */ + const loadReportingIntervalMs = + Number.parseInt(message.load_reporting_interval!.seconds) * 1000 + + message.load_reporting_interval!.nanos / 1_000_000; + trace('Received LRS response with load reporting interval ' + loadReportingIntervalMs + ' ms'); + this.statsTimer = setInterval(() => { + this.sendStats(); + }, loadReportingIntervalMs); + } + this.latestLrsSettings = message; + this.receivedLrsSettingsForCurrentStream = true; + } + + private handleLrsCallStatus(streamStatus: StatusObject) { + trace( + 'LRS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details + ); + this.lrsCall = null; + clearInterval(this.statsTimer); + /* If the backoff timer is no longer running, we do not need to wait any + * more to start the new call. */ + if (!this.lrsBackoff.isRunning()) { + this.maybeStartLrsStream(); + } + } + + private maybeStartLrsStreamV3(): boolean { + if (!this.lrsClient) { + return false; + } + if (this.lrsCall) { + return false; + } + this.lrsCall = this.lrsClient.streamLoadStats(); + this.receivedLrsSettingsForCurrentStream = false; + this.lrsCall.on('data', (message: LoadStatsResponse__Output) => { + this.handleLrsResponse(message); + }); + this.lrsCall.on('status', (status: StatusObject) => { + this.handleLrsCallStatus(status); + }); + this.lrsCall.on('error', () => {}); + return true; + } + + private maybeStartLrsStream() { + if (this.hasShutdown) { + return; + } + if (this.adsState.eds.getResourceNames().length === 0 && + this.adsState.cds.getResourceNames().length === 0 && + this.adsState.rds.getResourceNames().length === 0 && + this.adsState.lds.getResourceNames().length === 0) { + return; + } + if (!this.lrsClient) { + return; + } + if (this.lrsCall) { + return; + } + this.lrsCall = this.lrsClient.streamLoadStats(); + this.receivedLrsSettingsForCurrentStream = false; + this.lrsCall.on('data', (message: LoadStatsResponse__Output) => { + this.handleLrsResponse(message); + }); + this.lrsCall.on('status', (status: StatusObject) => { + this.handleLrsCallStatus(status); + }); + this.lrsCall.on('error', () => {}); + trace('Starting LRS stream'); + this.lrsBackoff.runOnce(); + /* Send buffered stats information when starting LRS stream. If there is no + * buffered stats information, it will still send the node field. */ + this.sendStats(); + } + + private maybeSendLrsMessage(clusterStats: ClusterStats[]) { + this.lrsCall?.write({ + node: this.lrsNode!, + cluster_stats: clusterStats + }); + } + + private sendStats() { + if (this.lrsCall === null) { + return; + } + if (!this.latestLrsSettings) { + this.maybeSendLrsMessage([]); + return; + } + const clusterStats: ClusterStats[] = []; + for (const [ + { clusterName, edsServiceName }, + stats, + ] of this.clusterStatsMap.entries()) { + if ( + this.latestLrsSettings.send_all_clusters || + this.latestLrsSettings.clusters.indexOf(clusterName) > 0 + ) { + const upstreamLocalityStats: UpstreamLocalityStats[] = []; + for (const localityStats of stats.localityStats) { + // Skip localities with 0 requests + if ( + localityStats.callsStarted > 0 || + localityStats.callsSucceeded > 0 || + localityStats.callsFailed > 0 + ) { + upstreamLocalityStats.push({ + locality: localityStats.locality, + total_issued_requests: localityStats.callsStarted, + total_successful_requests: localityStats.callsSucceeded, + total_error_requests: localityStats.callsFailed, + total_requests_in_progress: localityStats.callsInProgress, + }); + localityStats.callsStarted = 0; + localityStats.callsSucceeded = 0; + localityStats.callsFailed = 0; + } + } + const droppedRequests: DroppedRequests[] = []; + let totalDroppedRequests = 0; + for (const [category, count] of stats.callsDropped.entries()) { + if (count > 0) { + droppedRequests.push({ + category, + dropped_count: count, + }); + totalDroppedRequests += count; + } + } + totalDroppedRequests += stats.uncategorizedCallsDropped; + // Clear out dropped call stats after sending them + stats.callsDropped.clear(); + stats.uncategorizedCallsDropped = 0; + const interval = process.hrtime(stats.intervalStart); + stats.intervalStart = process.hrtime(); + // Skip clusters with 0 requests + if (upstreamLocalityStats.length > 0 || totalDroppedRequests > 0) { + clusterStats.push({ + cluster_name: clusterName, + cluster_service_name: edsServiceName, + dropped_requests: droppedRequests, + total_dropped_requests: totalDroppedRequests, + upstream_locality_stats: upstreamLocalityStats, + load_report_interval: { + seconds: interval[0], + nanos: interval[1], + }, + }); + } + } + } + trace('Sending LRS stats ' + JSON.stringify(clusterStats, undefined, 2)); + this.maybeSendLrsMessage(clusterStats); + } + + addEndpointWatcher( + edsServiceName: string, + watcher: Watcher + ) { + trace('Watcher added for endpoint ' + edsServiceName); + this.adsState.eds.addWatcher(edsServiceName, watcher); + } + + removeEndpointWatcher( + edsServiceName: string, + watcher: Watcher + ) { + trace('Watcher removed for endpoint ' + edsServiceName); + this.adsState.eds.removeWatcher(edsServiceName, watcher); + } + + addClusterWatcher(clusterName: string, watcher: Watcher) { + trace('Watcher added for cluster ' + clusterName); + this.adsState.cds.addWatcher(clusterName, watcher); + } + + removeClusterWatcher(clusterName: string, watcher: Watcher) { + trace('Watcher removed for cluster ' + clusterName); + this.adsState.cds.removeWatcher(clusterName, watcher); + } + + addRouteWatcher(routeConfigName: string, watcher: Watcher) { + trace('Watcher added for route ' + routeConfigName); + this.adsState.rds.addWatcher(routeConfigName, watcher); + } + + removeRouteWatcher(routeConfigName: string, watcher: Watcher) { + trace('Watcher removed for route ' + routeConfigName); + this.adsState.rds.removeWatcher(routeConfigName, watcher); + } + + addListenerWatcher(targetName: string, watcher: Watcher) { + trace('Watcher added for listener ' + targetName); + this.adsState.lds.addWatcher(targetName, watcher); + } + + removeListenerWatcher(targetName: string, watcher: Watcher) { + trace('Watcher removed for listener ' + targetName); + this.adsState.lds.removeWatcher(targetName, watcher); + } + + /** + * + * @param lrsServer The target name of the server to send stats to. An empty + * string indicates that the default LRS client should be used. Currently + * only the empty string is supported here. + * @param clusterName + * @param edsServiceName + */ + addClusterDropStats( + lrsServer: string, + clusterName: string, + edsServiceName: string + ): XdsClusterDropStats { + trace('addClusterDropStats(lrsServer=' + lrsServer + ', clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ')'); + if (lrsServer !== '') { + return { + addUncategorizedCallDropped: () => {}, + addCallDropped: (category) => {}, + }; + } + const clusterStats = this.clusterStatsMap.getOrCreate( + clusterName, + edsServiceName + ); + return { + addUncategorizedCallDropped: () => { + clusterStats.uncategorizedCallsDropped += 1; + }, + addCallDropped: (category) => { + const prevCount = clusterStats.callsDropped.get(category) ?? 0; + clusterStats.callsDropped.set(category, prevCount + 1); + }, + }; + } + + addClusterLocalityStats( + lrsServer: string, + clusterName: string, + edsServiceName: string, + locality: Locality__Output + ): XdsClusterLocalityStats { + trace('addClusterLocalityStats(lrsServer=' + lrsServer + ', clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ', locality=' + JSON.stringify(locality) + ')'); + if (lrsServer !== '') { + return { + addCallStarted: () => {}, + addCallFinished: (fail) => {}, + }; + } + const clusterStats = this.clusterStatsMap.getOrCreate( + clusterName, + edsServiceName + ); + let localityStats: ClusterLocalityStats | null = null; + for (const statsObj of clusterStats.localityStats) { + if (localityEqual(locality, statsObj.locality)) { + localityStats = statsObj; + break; + } + } + if (localityStats === null) { + localityStats = { + locality: locality, + callsInProgress: 0, + callsStarted: 0, + callsSucceeded: 0, + callsFailed: 0, + }; + clusterStats.localityStats.push(localityStats); + } + /* Help the compiler understand that this object is always non-null in the + * closure */ + const finalLocalityStats: ClusterLocalityStats = localityStats; + return { + addCallStarted: () => { + finalLocalityStats.callsStarted += 1; + finalLocalityStats.callsInProgress += 1; + }, + addCallFinished: (fail) => { + if (fail) { + finalLocalityStats.callsFailed += 1; + } else { + finalLocalityStats.callsSucceeded += 1; + } + finalLocalityStats.callsInProgress -= 1; + }, + }; + } + + private shutdown(): void { + this.adsCall?.cancel(); + this.adsClient?.close(); + this.lrsCall?.cancel(); + this.lrsClient?.close(); + this.hasShutdown = true; + } +} + +let singletonXdsClient: XdsClient | null = null; + +export function getSingletonXdsClient(): XdsClient { + if (singletonXdsClient === null) { + singletonXdsClient = new XdsClient(); + } + return singletonXdsClient; +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts new file mode 100644 index 000000000..9fd12d6ed --- /dev/null +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2021 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. + * + */ + +import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; +import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; +import { Duration__Output } from "../generated/google/protobuf/Duration"; +import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; +import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; + +export class CdsState extends BaseXdsStreamState implements XdsStreamState { + protected isStateOfTheWorld(): boolean { + return true; + } + protected getResourceName(resource: Cluster__Output): string { + return resource.name; + } + protected getProtocolName(): string { + return 'CDS'; + } + + private validateNonnegativeDuration(duration: Duration__Output | null): boolean { + if (!duration) { + return true; + } + /* The maximum values here come from the official Protobuf documentation: + * https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration + */ + return Number(duration.seconds) >= 0 && + Number(duration.seconds) <= 315_576_000_000 && + duration.nanos >= 0 && + duration.nanos <= 999_999_999; + } + + private validatePercentage(percentage: UInt32Value__Output | null): boolean { + if (!percentage) { + return true; + } + return percentage.value >=0 && percentage.value <= 100; + } + + public validateResponse(message: Cluster__Output): boolean { + if (message.type !== 'EDS') { + return false; + } + if (!message.eds_cluster_config?.eds_config?.ads) { + return false; + } + if (message.lb_policy !== 'ROUND_ROBIN') { + return false; + } + if (message.lrs_server) { + if (!message.lrs_server.self) { + return false; + } + } + if (EXPERIMENTAL_OUTLIER_DETECTION) { + if (message.outlier_detection) { + if (!this.validateNonnegativeDuration(message.outlier_detection.interval)) { + return false; + } + if (!this.validateNonnegativeDuration(message.outlier_detection.base_ejection_time)) { + return false; + } + if (!this.validateNonnegativeDuration(message.outlier_detection.max_ejection_time)) { + return false; + } + if (!this.validatePercentage(message.outlier_detection.max_ejection_percent)) { + return false; + } + if (!this.validatePercentage(message.outlier_detection.enforcing_success_rate)) { + return false; + } + if (!this.validatePercentage(message.outlier_detection.failure_percentage_threshold)) { + return false; + } + if (!this.validatePercentage(message.outlier_detection.enforcing_failure_percentage)) { + return false; + } + } + } + return true; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts new file mode 100644 index 000000000..b043ebbc0 --- /dev/null +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2021 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. + * + */ + +import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; +import { isIPv4, isIPv6 } from "net"; +import { Locality__Output } from "../generated/envoy/config/core/v3/Locality"; +import { SocketAddress__Output } from "../generated/envoy/config/core/v3/SocketAddress"; +import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { Any__Output } from "../generated/google/protobuf/Any"; +import { BaseXdsStreamState, HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; + +const TRACER_NAME = 'xds_client'; + +const UINT32_MAX = 0xFFFFFFFF; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +function localitiesEqual(a: Locality__Output, b: Locality__Output) { + return a.region === b.region && a.sub_zone === b.sub_zone && a.zone === b.zone; +} + +function addressesEqual(a: SocketAddress__Output, b: SocketAddress__Output) { + return a.address === b.address && a.port_value === b.port_value; +} + +export class EdsState extends BaseXdsStreamState implements XdsStreamState { + protected getResourceName(resource: ClusterLoadAssignment__Output): string { + return resource.cluster_name; + } + protected getProtocolName(): string { + return 'EDS'; + } + protected isStateOfTheWorld(): boolean { + return false; + } + + /** + * Validate the ClusterLoadAssignment object by these rules: + * https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md#clusterloadassignment-proto + * @param message + */ + public validateResponse(message: ClusterLoadAssignment__Output) { + const seenLocalities: {locality: Locality__Output, priority: number}[] = []; + const seenAddresses: SocketAddress__Output[] = []; + const priorityTotalWeights: Map = new Map(); + for (const endpoint of message.endpoints) { + if (!endpoint.locality) { + trace('EDS validation: endpoint locality unset'); + return false; + } + for (const {locality, priority} of seenLocalities) { + if (localitiesEqual(endpoint.locality, locality) && endpoint.priority === priority) { + trace('EDS validation: endpoint locality duplicated: ' + JSON.stringify(locality) + ', priority=' + priority); + return false; + } + } + seenLocalities.push({locality: endpoint.locality, priority: endpoint.priority}); + for (const lb of endpoint.lb_endpoints) { + const socketAddress = lb.endpoint?.address?.socket_address; + if (!socketAddress) { + trace('EDS validation: endpoint socket_address not set'); + return false; + } + if (socketAddress.port_specifier !== 'port_value') { + trace('EDS validation: socket_address.port_specifier !== "port_value"'); + return false; + } + if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) { + trace('EDS validation: address not a valid IPv4 or IPv6 address: ' + socketAddress.address); + return false; + } + for (const address of seenAddresses) { + if (addressesEqual(socketAddress, address)) { + trace('EDS validation: duplicate address seen: ' + address); + return false; + } + } + seenAddresses.push(socketAddress); + } + priorityTotalWeights.set(endpoint.priority, (priorityTotalWeights.get(endpoint.priority) ?? 0) + (endpoint.load_balancing_weight?.value ?? 0)); + } + for (const totalWeight of priorityTotalWeights.values()) { + if (totalWeight > UINT32_MAX) { + trace('EDS validation: total weight > UINT32_MAX') + return false; + } + } + for (const priority of priorityTotalWeights.keys()) { + if (priority > 0 && !priorityTotalWeights.has(priority - 1)) { + trace('EDS validation: priorities not contiguous'); + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts new file mode 100644 index 000000000..c215076db --- /dev/null +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2021 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. + * + */ + +import { experimental, logVerbosity } from "@grpc/grpc-js"; +import { Listener__Output } from '../generated/envoy/config/listener/v3/Listener'; +import { RdsState } from "./rds-state"; +import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; +import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from '../resources'; +import { getTopLevelFilterUrl, validateTopLevelFilter } from '../http-filter'; +import { EXPERIMENTAL_FAULT_INJECTION } from '../environment'; + +const TRACER_NAME = 'xds_client'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; + +export class LdsState extends BaseXdsStreamState implements XdsStreamState { + protected getResourceName(resource: Listener__Output): string { + return resource.name; + } + protected getProtocolName(): string { + return 'LDS'; + } + protected isStateOfTheWorld(): boolean { + return true; + } + + constructor(private rdsState: RdsState, updateResourceNames: () => void) { + super(updateResourceNames); + } + + public validateResponse(message: Listener__Output): boolean { + if ( + !( + message.api_listener?.api_listener && + message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL + ) + ) { + return false; + } + const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, message.api_listener!.api_listener.value); + if (EXPERIMENTAL_FAULT_INJECTION) { + const filterNames = new Set(); + for (const [index, httpFilter] of httpConnectionManager.http_filters.entries()) { + if (filterNames.has(httpFilter.name)) { + trace('LDS response validation failed: duplicate HTTP filter name ' + httpFilter.name); + return false; + } + filterNames.add(httpFilter.name); + if (!validateTopLevelFilter(httpFilter)) { + trace('LDS response validation failed: ' + httpFilter.name + ' filter validation failed'); + return false; + } + /* Validate that the last filter, and only the last filter, is the + * router filter. */ + const filterUrl = getTopLevelFilterUrl(httpFilter.typed_config!) + if (index < httpConnectionManager.http_filters.length - 1) { + if (filterUrl === ROUTER_FILTER_URL) { + trace('LDS response validation failed: router filter is before end of list'); + return false; + } + } else { + if (filterUrl !== ROUTER_FILTER_URL) { + trace('LDS response validation failed: final filter is ' + filterUrl); + return false; + } + } + } + } + switch (httpConnectionManager.route_specifier) { + case 'rds': + return !!httpConnectionManager.rds?.config_source?.ads; + case 'route_config': + return this.rdsState.validateResponse(httpConnectionManager.route_config!); + } + return false; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts new file mode 100644 index 000000000..fef694517 --- /dev/null +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -0,0 +1,156 @@ +/* + * Copyright 2021 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. + * + */ + +import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from "../environment"; +import { RetryPolicy__Output } from "../generated/envoy/config/route/v3/RetryPolicy"; +import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; +import { Duration__Output } from "../generated/google/protobuf/Duration"; +import { validateOverrideFilter } from "../http-filter"; +import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; + +const SUPPORTED_PATH_SPECIFIERS = ['prefix', 'path', 'safe_regex']; +const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ + 'exact_match', + 'safe_regex_match', + 'range_match', + 'present_match', + 'prefix_match', + 'suffix_match']; +const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; + +const UINT32_MAX = 0xFFFFFFFF; + +function durationToMs(duration: Duration__Output | null): number | null { + if (duration === null) { + return null; + } + return (Number.parseInt(duration.seconds) * 1000 + duration.nanos / 1_000_000) | 0; +} + +export class RdsState extends BaseXdsStreamState implements XdsStreamState { + protected isStateOfTheWorld(): boolean { + return false; + } + protected getResourceName(resource: RouteConfiguration__Output): string { + return resource.name; + } + protected getProtocolName(): string { + return 'RDS'; + } + + private validateRetryPolicy(policy: RetryPolicy__Output | null): boolean { + if (policy === null) { + return true; + } + const numRetries = policy.num_retries?.value ?? 1 + if (numRetries < 1) { + return false; + } + if (policy.retry_back_off) { + if (!policy.retry_back_off.base_interval) { + return false; + } + const baseInterval = durationToMs(policy.retry_back_off.base_interval)!; + const maxInterval = durationToMs(policy.retry_back_off.max_interval) ?? (10 * baseInterval); + if (!(maxInterval >= baseInterval) && (baseInterval > 0)) { + return false; + } + } + return true; + } + + validateResponse(message: RouteConfiguration__Output): boolean { + // https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation + for (const virtualHost of message.virtual_hosts) { + for (const domainPattern of virtualHost.domains) { + const starIndex = domainPattern.indexOf('*'); + const lastStarIndex = domainPattern.lastIndexOf('*'); + // A domain pattern can have at most one wildcard * + if (starIndex !== lastStarIndex) { + return false; + } + // A wildcard * can either be absent or at the beginning or end of the pattern + if (!(starIndex === -1 || starIndex === 0 || starIndex === domainPattern.length - 1)) { + return false; + } + } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const filterConfig of Object.values(virtualHost.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return false; + } + } + } + if (EXPERIMENTAL_RETRY) { + if (!this.validateRetryPolicy(virtualHost.retry_policy)) { + return false; + } + } + for (const route of virtualHost.routes) { + const match = route.match; + if (!match) { + return false; + } + if (SUPPORTED_PATH_SPECIFIERS.indexOf(match.path_specifier) < 0) { + return false; + } + for (const headers of match.headers) { + if (SUPPPORTED_HEADER_MATCH_SPECIFIERS.indexOf(headers.header_match_specifier) < 0) { + return false; + } + } + if (route.action !== 'route') { + return false; + } + if ((route.route === undefined) || (route.route === null) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { + return false; + } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const [name, filterConfig] of Object.entries(route.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return false; + } + } + } + if (EXPERIMENTAL_RETRY) { + if (!this.validateRetryPolicy(route.route.retry_policy)) { + return false; + } + } + if (route.route!.cluster_specifier === 'weighted_clusters') { + let weightSum = 0; + for (const clusterWeight of route.route.weighted_clusters!.clusters) { + weightSum += clusterWeight.weight?.value ?? 0; + } + if (weightSum === 0 || weightSum > UINT32_MAX) { + return false; + } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const weightedCluster of route.route!.weighted_clusters!.clusters) { + for (const filterConfig of Object.values(weightedCluster.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return false; + } + } + } + } + } + } + } + return true; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts new file mode 100644 index 000000000..b04adb79a --- /dev/null +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -0,0 +1,282 @@ +/* + * Copyright 2021 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. + * + */ + +import { experimental, logVerbosity, Metadata, status, StatusObject } from "@grpc/grpc-js"; +import { Any__Output } from "../generated/google/protobuf/Any"; + +const TRACER_NAME = 'xds_client'; + +export interface Watcher { + /* Including the isV2 flag here is a bit of a kludge. It would probably be + * better for XdsStreamState#handleResponses to transform the protobuf + * message type into a library-specific configuration object type, to + * remove a lot of duplicate logic, including logic for handling that + * flag. */ + onValidUpdate(update: UpdateType): void; + onTransientError(error: StatusObject): void; + onResourceDoesNotExist(): void; +} + +export interface ResourcePair { + resource: ResourceType; + raw: Any__Output; +} + +export interface AcceptedResourceEntry { + name: string; + raw: Any__Output; +} + +export interface RejectedResourceEntry { + name: string; + raw: Any__Output; + error: string; +} + +export interface HandleResponseResult { + accepted: AcceptedResourceEntry[]; + rejected: RejectedResourceEntry[]; + missing: string[]; +} + +export interface XdsStreamState { + versionInfo: string; + nonce: string; + getResourceNames(): string[]; + /** + * Returns a string containing the error details if the message should be nacked, + * or null if it should be acked. + * @param responses + */ + handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult; + + reportStreamError(status: StatusObject): void; + reportAdsStreamStart(): void; + + addWatcher(name: string, watcher: Watcher): void; + removeWatcher(resourceName: string, watcher: Watcher): void; +} + +interface SubscriptionEntry { + watchers: Watcher[]; + cachedResponse: ResponseType | null; + resourceTimer: NodeJS.Timer; + deletionIgnored: boolean; +} + +const RESOURCE_TIMEOUT_MS = 15_000; + +export abstract class BaseXdsStreamState implements XdsStreamState { + versionInfo = ''; + nonce = ''; + + private subscriptions: Map> = new Map>(); + private isAdsStreamRunning = false; + private ignoreResourceDeletion = false; + + constructor(private updateResourceNames: () => void) {} + + protected trace(text: string) { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, this.getProtocolName() + ' | ' + text); + } + + private startResourceTimer(subscriptionEntry: SubscriptionEntry) { + clearTimeout(subscriptionEntry.resourceTimer); + subscriptionEntry.resourceTimer = setTimeout(() => { + for (const watcher of subscriptionEntry.watchers) { + watcher.onResourceDoesNotExist(); + } + }, RESOURCE_TIMEOUT_MS); + } + + addWatcher(name: string, watcher: Watcher): void { + this.trace('Adding watcher for name ' + name); + let subscriptionEntry = this.subscriptions.get(name); + let addedName = false; + if (subscriptionEntry === undefined) { + addedName = true; + subscriptionEntry = { + watchers: [], + cachedResponse: null, + resourceTimer: setTimeout(() => {}, 0), + deletionIgnored: false + }; + if (this.isAdsStreamRunning) { + this.startResourceTimer(subscriptionEntry); + } + this.subscriptions.set(name, subscriptionEntry); + } + subscriptionEntry.watchers.push(watcher); + if (subscriptionEntry.cachedResponse !== null) { + const cachedResponse = subscriptionEntry.cachedResponse; + /* These updates normally occur asynchronously, so we ensure that + * the same happens here */ + process.nextTick(() => { + this.trace('Reporting existing update for new watcher for name ' + name); + watcher.onValidUpdate(cachedResponse); + }); + } + if (addedName) { + this.updateResourceNames(); + } + } + removeWatcher(resourceName: string, watcher: Watcher): void { + this.trace('Removing watcher for name ' + resourceName); + const subscriptionEntry = this.subscriptions.get(resourceName); + if (subscriptionEntry !== undefined) { + const entryIndex = subscriptionEntry.watchers.indexOf(watcher); + if (entryIndex >= 0) { + subscriptionEntry.watchers.splice(entryIndex, 1); + } + if (subscriptionEntry.watchers.length === 0) { + clearTimeout(subscriptionEntry.resourceTimer); + if (subscriptionEntry.deletionIgnored) { + experimental.log(logVerbosity.INFO, 'Unsubscribing from resource with previously ignored deletion: ' + resourceName); + } + this.subscriptions.delete(resourceName); + this.updateResourceNames(); + } + } + } + + getResourceNames(): string[] { + return Array.from(this.subscriptions.keys()); + } + handleResponses(responses: ResourcePair[]): HandleResponseResult { + let result: HandleResponseResult = { + accepted: [], + rejected: [], + missing: [] + } + const allResourceNames = new Set(); + for (const {resource, raw} of responses) { + const resourceName = this.getResourceName(resource); + allResourceNames.add(resourceName); + const subscriptionEntry = this.subscriptions.get(resourceName); + if (this.validateResponse(resource)) { + result.accepted.push({ + name: resourceName, + raw: raw}); + if (subscriptionEntry) { + for (const watcher of subscriptionEntry.watchers) { + watcher.onValidUpdate(resource); + } + clearTimeout(subscriptionEntry.resourceTimer); + subscriptionEntry.cachedResponse = resource; + if (subscriptionEntry.deletionIgnored) { + experimental.log(logVerbosity.INFO, `Received resource with previously ignored deletion: ${resourceName}`); + subscriptionEntry.deletionIgnored = false; + } + } + } else { + this.trace('Validation failed for message ' + JSON.stringify(resource)); + result.rejected.push({ + name: resourceName, + raw: raw, + error: `Validation failed for resource ${resourceName}` + }); + if (subscriptionEntry) { + for (const watcher of subscriptionEntry.watchers) { + watcher.onTransientError({ + code: status.UNAVAILABLE, + details: `Validation failed for resource ${resourceName}`, + metadata: new Metadata() + }); + } + clearTimeout(subscriptionEntry.resourceTimer); + } + } + } + result.missing = this.handleMissingNames(allResourceNames); + this.trace('Received response with resource names [' + Array.from(allResourceNames) + ']'); + return result; + } + reportStreamError(status: StatusObject): void { + for (const subscriptionEntry of this.subscriptions.values()) { + for (const watcher of subscriptionEntry.watchers) { + watcher.onTransientError(status); + } + clearTimeout(subscriptionEntry.resourceTimer); + } + this.isAdsStreamRunning = false; + this.nonce = ''; + } + + reportAdsStreamStart() { + if (this.isAdsStreamRunning) { + return; + } + this.isAdsStreamRunning = true; + for (const subscriptionEntry of this.subscriptions.values()) { + if (subscriptionEntry.cachedResponse === null) { + this.startResourceTimer(subscriptionEntry); + } + } + } + + private handleMissingNames(allResponseNames: Set): string[] { + if (this.isStateOfTheWorld()) { + const missingNames: string[] = []; + for (const [resourceName, subscriptionEntry] of this.subscriptions.entries()) { + if (!allResponseNames.has(resourceName) && subscriptionEntry.cachedResponse !== null) { + if (this.ignoreResourceDeletion) { + if (!subscriptionEntry.deletionIgnored) { + experimental.log(logVerbosity.ERROR, 'Ignoring nonexistent resource ' + resourceName); + subscriptionEntry.deletionIgnored = true; + } + } else { + this.trace('Reporting resource does not exist named ' + resourceName); + missingNames.push(resourceName); + for (const watcher of subscriptionEntry.watchers) { + watcher.onResourceDoesNotExist(); + } + subscriptionEntry.cachedResponse = null; + } + } + } + return missingNames; + } else { + return []; + } + } + + enableIgnoreResourceDeletion() { + this.ignoreResourceDeletion = true; + } + + /** + * Apply the validation rules for this resource type to this resource + * instance. + * This function is public so that the LDS validateResponse can call into + * the RDS validateResponse. + * @param resource The resource object sent by the xDS server + */ + public abstract validateResponse(resource: ResponseType): boolean; + /** + * Get the name of a resource object. The name is some field of the object, so + * getting it depends on the specific type. + * @param resource + */ + protected abstract getResourceName(resource: ResponseType): string; + protected abstract getProtocolName(): string; + /** + * Indicates whether responses are "state of the world", i.e. that they + * contain all resources and that omitted previously-seen resources should + * be treated as removed. + */ + protected abstract isStateOfTheWorld(): boolean; +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/backend.ts b/packages/grpc-js-xds/test/backend.ts new file mode 100644 index 000000000..ce509c556 --- /dev/null +++ b/packages/grpc-js-xds/test/backend.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2023 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. + * + */ + +import { loadPackageDefinition, sendUnaryData, Server, ServerCredentials, ServerUnaryCall, UntypedServiceImplementation } from "@grpc/grpc-js"; +import { loadSync } from "@grpc/proto-loader"; +import { ProtoGrpcType } from "./generated/echo"; +import { EchoRequest__Output } from "./generated/grpc/testing/EchoRequest"; +import { EchoResponse } from "./generated/grpc/testing/EchoResponse"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + 'grpc/testing/echo.proto' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to build/test + __dirname + '/../../proto/' + ], + })) as unknown as ProtoGrpcType; + +export class Backend { + private server: Server; + private receivedCallCount = 0; + private callListeners: (() => void)[] = []; + private port: number | null = null; + constructor() { + this.server = new Server(); + this.server.addService(loadedProtos.grpc.testing.EchoTestService.service, this as unknown as UntypedServiceImplementation); + } + Echo(call: ServerUnaryCall, callback: sendUnaryData) { + // call.request.params is currently ignored + this.addCall(); + callback(null, {message: call.request.message}); + } + + addCall() { + this.receivedCallCount++; + this.callListeners.forEach(listener => listener()); + } + + onCall(listener: () => void) { + this.callListeners.push(listener); + } + + start(callback: (error: Error | null, port: number) => void) { + this.server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (error, port) => { + if (!error) { + this.port = port; + this.server.start(); + } + callback(error, port); + }) + } + + startAsync(): Promise { + return new Promise((resolve, reject) => { + this.start((error, port) => { + if (error) { + reject(error); + } else { + resolve(port); + } + }); + }); + } + + getPort(): number { + if (this.port === null) { + throw new Error('Port not set. Backend not yet started.'); + } + return this.port; + } + + getCallCount() { + return this.receivedCallCount; + } + + resetCallCount() { + this.receivedCallCount = 0; + } + + shutdown(callback: (error?: Error) => void) { + this.server.tryShutdown(callback); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/client.ts b/packages/grpc-js-xds/test/client.ts new file mode 100644 index 000000000..6404a3eb2 --- /dev/null +++ b/packages/grpc-js-xds/test/client.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2023 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. + * + */ + +import { credentials, loadPackageDefinition } from "@grpc/grpc-js"; +import { loadSync } from "@grpc/proto-loader"; +import { ProtoGrpcType } from "./generated/echo"; +import { EchoTestServiceClient } from "./generated/grpc/testing/EchoTestService"; +import { XdsServer } from "./xds-server"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + 'grpc/testing/echo.proto' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to build/test + __dirname + '/../../proto/' + ], + })) as unknown as ProtoGrpcType; + +const BOOTSTRAP_CONFIG_KEY = 'grpc.TEST_ONLY_DO_NOT_USE_IN_PROD.xds_bootstrap_config'; + +export class XdsTestClient { + private client: EchoTestServiceClient; + private callInterval: NodeJS.Timer; + + constructor(targetName: string, xdsServer: XdsServer) { + this.client = new loadedProtos.grpc.testing.EchoTestService(`xds:///${targetName}`, credentials.createInsecure(), {[BOOTSTRAP_CONFIG_KEY]: xdsServer.getBootstrapInfoString()}); + this.callInterval = setInterval(() => {}, 0); + clearInterval(this.callInterval); + } + + startCalls(interval: number) { + clearInterval(this.callInterval); + this.callInterval = setInterval(() => { + this.client.echo({message: 'test'}, (error, value) => { + if (error) { + throw error; + } + }); + }, interval); + } + + stopCalls() { + clearInterval(this.callInterval); + } + + close() { + this.stopCalls(); + this.client.close(); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts new file mode 100644 index 000000000..945b08a3a --- /dev/null +++ b/packages/grpc-js-xds/test/framework.ts @@ -0,0 +1,208 @@ +/* + * Copyright 2023 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. + * + */ + +import { ClusterLoadAssignment } from "../src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { Cluster } from "../src/generated/envoy/config/cluster/v3/Cluster"; +import { Backend } from "./backend"; +import { Locality } from "../src/generated/envoy/config/core/v3/Locality"; +import { RouteConfiguration } from "../src/generated/envoy/config/route/v3/RouteConfiguration"; +import { Route } from "../src/generated/envoy/config/route/v3/Route"; +import { Listener } from "../src/generated/envoy/config/listener/v3/Listener"; +import { HttpConnectionManager } from "../src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager"; +import { AnyExtension } from "@grpc/proto-loader"; +import { HTTP_CONNECTION_MANGER_TYPE_URL } from "../src/resources"; +import { LocalityLbEndpoints } from "../src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints"; +import { LbEndpoint } from "../src/generated/envoy/config/endpoint/v3/LbEndpoint"; + +interface Endpoint { + locality: Locality; + backends: Backend[]; + weight?: number; + priority?: number; +} + +function getLbEndpoint(backend: Backend): LbEndpoint { + return { + health_status: "HEALTHY", + endpoint: { + address: { + socket_address: { + address: '::1', + port_value: backend.getPort() + } + } + } + }; +} + +function getLocalityLbEndpoints(endpoint: Endpoint): LocalityLbEndpoints { + return { + lb_endpoints: endpoint.backends.map(getLbEndpoint), + locality: endpoint.locality, + load_balancing_weight: {value: endpoint.weight ?? 1}, + priority: endpoint.priority ?? 0 + } +} + +export class FakeCluster { + constructor(private name: string, private endpoints: Endpoint[]) {} + + getEndpointConfig(): ClusterLoadAssignment { + return { + cluster_name: this.name, + endpoints: this.endpoints.map(getLocalityLbEndpoints) + }; + } + + getClusterConfig(): Cluster { + return { + name: this.name, + type: 'EDS', + eds_cluster_config: {eds_config: {ads: {}}}, + lb_policy: 'ROUND_ROBIN' + } + } + + getName() { + return this.name; + } + + startAllBackends(): Promise { + return Promise.all(this.endpoints.map(endpoint => Promise.all(endpoint.backends.map(backend => backend.startAsync())))); + } + + private haveAllBackendsReceivedTraffic(): boolean { + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + if (backend.getCallCount() < 1) { + return false; + } + } + } + return true; + } + + waitForAllBackendsToReceiveTraffic(): Promise { + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + backend.resetCallCount(); + } + } + return new Promise((resolve, reject) => { + let finishedPromise = false; + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + backend.onCall(() => { + if (finishedPromise) { + return; + } + if (this.haveAllBackendsReceivedTraffic()) { + finishedPromise = true; + resolve(); + } + }); + } + } + }); + } +} + +interface FakeRoute { + cluster?: FakeCluster; + weightedClusters?: [{cluster: FakeCluster, weight: number}]; +} + +function createRouteConfig(route: FakeRoute): Route { + if (route.cluster) { + return { + match: { + prefix: '' + }, + route: { + cluster: route.cluster.getName() + } + }; + } else { + return { + match: { + prefix: '' + }, + route: { + weighted_clusters: { + clusters: route.weightedClusters!.map(clusterWeight => ({ + name: clusterWeight.cluster.getName(), + weight: {value: clusterWeight.weight} + })) + } + } + } + } +} + +export class FakeRouteGroup { + constructor(private name: string, private routes: FakeRoute[]) {} + + getRouteConfiguration(): RouteConfiguration { + return { + name: this.name, + virtual_hosts: [{ + domains: ['*'], + routes: this.routes.map(createRouteConfig) + }] + }; + } + + getListener(): Listener { + const httpConnectionManager: HttpConnectionManager & AnyExtension = { + '@type': HTTP_CONNECTION_MANGER_TYPE_URL, + rds: { + route_config_name: this.name, + config_source: {ads: {}} + } + } + return { + name: this.name, + api_listener: { + api_listener: httpConnectionManager + } + }; + } + + startAllBackends(): Promise { + return Promise.all(this.routes.map(route => { + if (route.cluster) { + return route.cluster.startAllBackends(); + } else if (route.weightedClusters) { + return Promise.all(route.weightedClusters.map(clusterWeight => clusterWeight.cluster.startAllBackends())); + } else { + return Promise.resolve(); + } + })); + } + + waitForAllBackendsToReceiveTraffic(): Promise { + return Promise.all(this.routes.map(route => { + if (route.cluster) { + return route.cluster.waitForAllBackendsToReceiveTraffic(); + } else if (route.weightedClusters) { + return Promise.all(route.weightedClusters.map(clusterWeight => clusterWeight.cluster.waitForAllBackendsToReceiveTraffic())).then(() => {}); + } else { + return Promise.resolve(); + } + })); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/generated/echo.ts b/packages/grpc-js-xds/test/generated/echo.ts new file mode 100644 index 000000000..537a49cfa --- /dev/null +++ b/packages/grpc-js-xds/test/generated/echo.ts @@ -0,0 +1,46 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { EchoTest1ServiceClient as _grpc_testing_EchoTest1ServiceClient, EchoTest1ServiceDefinition as _grpc_testing_EchoTest1ServiceDefinition } from './grpc/testing/EchoTest1Service'; +import type { EchoTest2ServiceClient as _grpc_testing_EchoTest2ServiceClient, EchoTest2ServiceDefinition as _grpc_testing_EchoTest2ServiceDefinition } from './grpc/testing/EchoTest2Service'; +import type { EchoTestServiceClient as _grpc_testing_EchoTestServiceClient, EchoTestServiceDefinition as _grpc_testing_EchoTestServiceDefinition } from './grpc/testing/EchoTestService'; +import type { NoRpcServiceClient as _grpc_testing_NoRpcServiceClient, NoRpcServiceDefinition as _grpc_testing_NoRpcServiceDefinition } from './grpc/testing/NoRpcService'; +import type { UnimplementedEchoServiceClient as _grpc_testing_UnimplementedEchoServiceClient, UnimplementedEchoServiceDefinition as _grpc_testing_UnimplementedEchoServiceDefinition } from './grpc/testing/UnimplementedEchoService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + grpc: { + testing: { + DebugInfo: MessageTypeDefinition + EchoRequest: MessageTypeDefinition + EchoResponse: MessageTypeDefinition + EchoTest1Service: SubtypeConstructor & { service: _grpc_testing_EchoTest1ServiceDefinition } + EchoTest2Service: SubtypeConstructor & { service: _grpc_testing_EchoTest2ServiceDefinition } + EchoTestService: SubtypeConstructor & { service: _grpc_testing_EchoTestServiceDefinition } + ErrorStatus: MessageTypeDefinition + /** + * A service without any rpc defined to test coverage. + */ + NoRpcService: SubtypeConstructor & { service: _grpc_testing_NoRpcServiceDefinition } + RequestParams: MessageTypeDefinition + ResponseParams: MessageTypeDefinition + SimpleRequest: MessageTypeDefinition + SimpleResponse: MessageTypeDefinition + StringValue: MessageTypeDefinition + UnimplementedEchoService: SubtypeConstructor & { service: _grpc_testing_UnimplementedEchoServiceDefinition } + } + } + xds: { + data: { + orca: { + v3: { + OrcaLoadReport: MessageTypeDefinition + } + } + } + } +} + diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts b/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts new file mode 100644 index 000000000..123188fe3 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts @@ -0,0 +1,18 @@ +// Original file: proto/grpc/testing/echo_messages.proto + + +/** + * Message to be echoed back serialized in trailer. + */ +export interface DebugInfo { + 'stack_entries'?: (string)[]; + 'detail'?: (string); +} + +/** + * Message to be echoed back serialized in trailer. + */ +export interface DebugInfo__Output { + 'stack_entries': (string)[]; + 'detail': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts new file mode 100644 index 000000000..cadf04f7a --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts @@ -0,0 +1,13 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { RequestParams as _grpc_testing_RequestParams, RequestParams__Output as _grpc_testing_RequestParams__Output } from '../../grpc/testing/RequestParams'; + +export interface EchoRequest { + 'message'?: (string); + 'param'?: (_grpc_testing_RequestParams | null); +} + +export interface EchoRequest__Output { + 'message': (string); + 'param': (_grpc_testing_RequestParams__Output | null); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts new file mode 100644 index 000000000..d54beaf4f --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts @@ -0,0 +1,13 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { ResponseParams as _grpc_testing_ResponseParams, ResponseParams__Output as _grpc_testing_ResponseParams__Output } from '../../grpc/testing/ResponseParams'; + +export interface EchoResponse { + 'message'?: (string); + 'param'?: (_grpc_testing_ResponseParams | null); +} + +export interface EchoResponse__Output { + 'message': (string); + 'param': (_grpc_testing_ResponseParams__Output | null); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts new file mode 100644 index 000000000..a2b1947f6 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts @@ -0,0 +1,117 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; + +export interface EchoTest1ServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface EchoTest1ServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTest1ServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts new file mode 100644 index 000000000..033e70143 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts @@ -0,0 +1,117 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; + +export interface EchoTest2ServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface EchoTest2ServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTest2ServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts new file mode 100644 index 000000000..d1fa2d075 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts @@ -0,0 +1,150 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; +import type { StringValue as _grpc_testing_StringValue, StringValue__Output as _grpc_testing_StringValue__Output } from '../../grpc/testing/StringValue'; + +export interface EchoTestServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + UnimplementedBidi(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + UnimplementedBidi(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + unimplementedBidi(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + unimplementedBidi(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + +} + +export interface EchoTestServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + CheckDeadlineSet: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue>; + + CheckDeadlineUpperBound: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + UnimplementedBidi: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTestServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + CheckDeadlineSet: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_StringValue, _grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue__Output> + CheckDeadlineUpperBound: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_StringValue, _grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + UnimplementedBidi: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts b/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts new file mode 100644 index 000000000..42ff36d9a --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts @@ -0,0 +1,20 @@ +// Original file: proto/grpc/testing/echo_messages.proto + + +/** + * Error status client expects to see. + */ +export interface ErrorStatus { + 'code'?: (number); + 'error_message'?: (string); + 'binary_error_details'?: (string); +} + +/** + * Error status client expects to see. + */ +export interface ErrorStatus__Output { + 'code': (number); + 'error_message': (string); + 'binary_error_details': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts new file mode 100644 index 000000000..7427c8097 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts @@ -0,0 +1,19 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' + +/** + * A service without any rpc defined to test coverage. + */ +export interface NoRpcServiceClient extends grpc.Client { +} + +/** + * A service without any rpc defined to test coverage. + */ +export interface NoRpcServiceHandlers extends grpc.UntypedServiceImplementation { +} + +export interface NoRpcServiceDefinition extends grpc.ServiceDefinition { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts b/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts new file mode 100644 index 000000000..e8c5ef1d1 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts @@ -0,0 +1,75 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { DebugInfo as _grpc_testing_DebugInfo, DebugInfo__Output as _grpc_testing_DebugInfo__Output } from '../../grpc/testing/DebugInfo'; +import type { ErrorStatus as _grpc_testing_ErrorStatus, ErrorStatus__Output as _grpc_testing_ErrorStatus__Output } from '../../grpc/testing/ErrorStatus'; +import type { OrcaLoadReport as _xds_data_orca_v3_OrcaLoadReport, OrcaLoadReport__Output as _xds_data_orca_v3_OrcaLoadReport__Output } from '../../xds/data/orca/v3/OrcaLoadReport'; + +export interface RequestParams { + 'echo_deadline'?: (boolean); + 'client_cancel_after_us'?: (number); + 'server_cancel_after_us'?: (number); + 'echo_metadata'?: (boolean); + 'check_auth_context'?: (boolean); + 'response_message_length'?: (number); + 'echo_peer'?: (boolean); + /** + * will force check_auth_context. + */ + 'expected_client_identity'?: (string); + 'skip_cancelled_check'?: (boolean); + 'expected_transport_security_type'?: (string); + 'debug_info'?: (_grpc_testing_DebugInfo | null); + /** + * Server should not see a request with this set. + */ + 'server_die'?: (boolean); + 'binary_error_details'?: (string); + 'expected_error'?: (_grpc_testing_ErrorStatus | null); + /** + * sleep when invoking server for deadline tests + */ + 'server_sleep_us'?: (number); + /** + * which backend to send request to + */ + 'backend_channel_idx'?: (number); + 'echo_metadata_initially'?: (boolean); + 'server_notify_client_when_started'?: (boolean); + 'backend_metrics'?: (_xds_data_orca_v3_OrcaLoadReport | null); + 'echo_host_from_authority_header'?: (boolean); +} + +export interface RequestParams__Output { + 'echo_deadline': (boolean); + 'client_cancel_after_us': (number); + 'server_cancel_after_us': (number); + 'echo_metadata': (boolean); + 'check_auth_context': (boolean); + 'response_message_length': (number); + 'echo_peer': (boolean); + /** + * will force check_auth_context. + */ + 'expected_client_identity': (string); + 'skip_cancelled_check': (boolean); + 'expected_transport_security_type': (string); + 'debug_info': (_grpc_testing_DebugInfo__Output | null); + /** + * Server should not see a request with this set. + */ + 'server_die': (boolean); + 'binary_error_details': (string); + 'expected_error': (_grpc_testing_ErrorStatus__Output | null); + /** + * sleep when invoking server for deadline tests + */ + 'server_sleep_us': (number); + /** + * which backend to send request to + */ + 'backend_channel_idx': (number); + 'echo_metadata_initially': (boolean); + 'server_notify_client_when_started': (boolean); + 'backend_metrics': (_xds_data_orca_v3_OrcaLoadReport__Output | null); + 'echo_host_from_authority_header': (boolean); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts b/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts new file mode 100644 index 000000000..588e463c2 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts @@ -0,0 +1,15 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface ResponseParams { + 'request_deadline'?: (number | string | Long); + 'host'?: (string); + 'peer'?: (string); +} + +export interface ResponseParams__Output { + 'request_deadline': (string); + 'host': (string); + 'peer': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts new file mode 100644 index 000000000..292a2020c --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts @@ -0,0 +1,8 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface SimpleRequest { +} + +export interface SimpleRequest__Output { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts new file mode 100644 index 000000000..3e8735e5e --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts @@ -0,0 +1,8 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface SimpleResponse { +} + +export interface SimpleResponse__Output { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts b/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts new file mode 100644 index 000000000..4a779ae2b --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts @@ -0,0 +1,10 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface StringValue { + 'message'?: (string); +} + +export interface StringValue__Output { + 'message': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts new file mode 100644 index 000000000..48128976e --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts @@ -0,0 +1,27 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; + +export interface UnimplementedEchoServiceClient extends grpc.Client { + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface UnimplementedEchoServiceHandlers extends grpc.UntypedServiceImplementation { + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface UnimplementedEchoServiceDefinition extends grpc.ServiceDefinition { + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts b/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts new file mode 100644 index 000000000..d66c42713 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts @@ -0,0 +1,59 @@ +// Original file: proto/grpc/testing/xds/v3/orca_load_report.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface OrcaLoadReport { + /** + * CPU utilization expressed as a fraction of available CPU resources. This + * should be derived from the latest sample or measurement. + */ + 'cpu_utilization'?: (number | string); + /** + * Memory utilization expressed as a fraction of available memory + * resources. This should be derived from the latest sample or measurement. + */ + 'mem_utilization'?: (number | string); + /** + * Total RPS being served by an endpoint. This should cover all services that an endpoint is + * responsible for. + */ + 'rps'?: (number | string | Long); + /** + * Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + * storage) associated with the request. + */ + 'request_cost'?: ({[key: string]: number | string}); + /** + * Resource utilization values. Each value is expressed as a fraction of total resources + * available, derived from the latest sample or measurement. + */ + 'utilization'?: ({[key: string]: number | string}); +} + +export interface OrcaLoadReport__Output { + /** + * CPU utilization expressed as a fraction of available CPU resources. This + * should be derived from the latest sample or measurement. + */ + 'cpu_utilization': (number | string); + /** + * Memory utilization expressed as a fraction of available memory + * resources. This should be derived from the latest sample or measurement. + */ + 'mem_utilization': (number | string); + /** + * Total RPS being served by an endpoint. This should cover all services that an endpoint is + * responsible for. + */ + 'rps': (string); + /** + * Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + * storage) associated with the request. + */ + 'request_cost': ({[key: string]: number | string}); + /** + * Resource utilization values. Each value is expressed as a fraction of total resources + * available, derived from the latest sample or measurement. + */ + 'utilization': ({[key: string]: number | string}); +} diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts new file mode 100644 index 000000000..d0d1b6031 --- /dev/null +++ b/packages/grpc-js-xds/test/test-core.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2023 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. + * + */ + +import { Backend } from "./backend"; +import { XdsTestClient } from "./client"; +import { FakeCluster, FakeRouteGroup } from "./framework"; +import { XdsServer } from "./xds-server"; + +import { register } from "../src"; +import assert = require("assert"); + +register(); + +describe('core xDS functionality', () => { + let xdsServer: XdsServer; + let client: XdsTestClient; + beforeEach(done => { + xdsServer = new XdsServer(); + xdsServer.startServer(error => { + done(error); + }); + }); + afterEach(() => { + client?.close(); + xdsServer?.shutdownServer(); + }) + it('should route requests to the single backend', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + client.stopCalls(); + assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`); + } + }) + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }, reason => done(reason)); + }, reason => done(reason)); + }); +}); \ No newline at end of file diff --git a/packages/grpc-js-xds/test/test-nack.ts b/packages/grpc-js-xds/test/test-nack.ts new file mode 100644 index 000000000..9395628a6 --- /dev/null +++ b/packages/grpc-js-xds/test/test-nack.ts @@ -0,0 +1,160 @@ +/* + * Copyright 2023 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. + * + */ + +import * as assert from 'assert'; +import { register } from "../src"; +import { Backend } from "./backend"; +import { XdsTestClient } from "./client"; +import { FakeCluster, FakeRouteGroup } from "./framework"; +import { XdsServer } from "./xds-server"; + +register(); + +describe('Validation errors', () => { + let xdsServer: XdsServer; + let client: XdsTestClient; + beforeEach(done => { + xdsServer = new XdsServer(); + xdsServer.startServer(error => { + done(error); + }); + }); + afterEach(() => { + client?.close(); + xdsServer?.shutdownServer(); + }); + it('Should continue to use a valid resource after receiving an invalid EDS update', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + // After backends receive calls, set invalid EDS resource + const invalidEdsResource = {cluster_name: cluster.getEndpointConfig().cluster_name, endpoints: [{}]}; + xdsServer.setEdsResource(invalidEdsResource); + let seenNack = false; + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + if (seenNack) { + return; + } + seenNack = true; + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }); + } + }); + }, reason => done(reason)); + }, reason => done(reason)); + }); + it('Should continue to use a valid resource after receiving an invalid CDS update', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + // After backends receive calls, set invalid CDS resource + const invalidCdsResource = {name: cluster.getClusterConfig().name}; + xdsServer.setCdsResource(invalidCdsResource); + let seenNack = false; + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + if (seenNack) { + return; + } + seenNack = true; + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }); + } + }); + }, reason => done(reason)); + }, reason => done(reason)); + }); + it('Should continue to use a valid resource after receiving an invalid RDS update', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + // After backends receive calls, set invalid RDS resource + const invalidRdsResource = {name: routeGroup.getRouteConfiguration().name, virtual_hosts: [{domains: ['**']}]}; + xdsServer.setRdsResource(invalidRdsResource); + let seenNack = false; + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + if (seenNack) { + return; + } + seenNack = true; + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }); + } + }); + }, reason => done(reason)); + }, reason => done(reason)); + }); + it('Should continue to use a valid resource after receiving an invalid LDS update', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + // After backends receive calls, set invalid LDS resource + const invalidLdsResource = {name: routeGroup.getListener().name}; + xdsServer.setLdsResource(invalidLdsResource); + let seenNack = false; + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + if (seenNack) { + return; + } + seenNack = true; + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }); + } + }); + }, reason => done(reason)); + }, reason => done(reason)); + }); +}); diff --git a/packages/grpc-native-core/src/grpc_extension.js b/packages/grpc-js-xds/test/test-register.ts similarity index 63% rename from packages/grpc-native-core/src/grpc_extension.js rename to packages/grpc-js-xds/test/test-register.ts index 77476f012..e7a07927a 100644 --- a/packages/grpc-native-core/src/grpc_extension.js +++ b/packages/grpc-js-xds/test/test-register.ts @@ -1,6 +1,5 @@ -/** - * @license - * Copyright 2016 gRPC authors. +/* + * Copyright 2020 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +15,13 @@ * */ -/** - * @module - * @private - */ - -'use strict'; - -var binary = require('node-pre-gyp/lib/pre-binding'); -var path = require('path'); -var binding_path = - binary.find(path.resolve(path.join(__dirname, '../package.json'))); -var binding = require(binding_path); +import * as assert from 'assert'; +import { register } from '../src'; -module.exports = binding; +/* This is just a basic test to confirm that the package builds and the setup + * code runs. */ +describe('register function', () => { + it('Should succeed without errors', () => { + assert.doesNotThrow(register); + }); +}); \ No newline at end of file diff --git a/packages/grpc-js-xds/test/xds-server.ts b/packages/grpc-js-xds/test/xds-server.ts new file mode 100644 index 000000000..b82a3a673 --- /dev/null +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -0,0 +1,342 @@ +/* + * Copyright 2023 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. + * + */ + +import { ServerDuplexStream, Server, UntypedServiceImplementation, ServerCredentials, loadPackageDefinition } from "@grpc/grpc-js"; +import { AnyExtension, loadSync } from "@grpc/proto-loader"; +import { EventEmitter } from "stream"; +import { Cluster } from "../src/generated/envoy/config/cluster/v3/Cluster"; +import { ClusterLoadAssignment } from "../src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { Listener } from "../src/generated/envoy/config/listener/v3/Listener"; +import { RouteConfiguration } from "../src/generated/envoy/config/route/v3/RouteConfiguration"; +import { AggregatedDiscoveryServiceHandlers } from "../src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService"; +import { DiscoveryRequest__Output } from "../src/generated/envoy/service/discovery/v3/DiscoveryRequest"; +import { DiscoveryResponse } from "../src/generated/envoy/service/discovery/v3/DiscoveryResponse"; +import { Any } from "../src/generated/google/protobuf/Any"; +import { LDS_TYPE_URL, RDS_TYPE_URL, CDS_TYPE_URL, EDS_TYPE_URL, LdsTypeUrl, RdsTypeUrl, CdsTypeUrl, EdsTypeUrl, AdsTypeUrl } from "../src/resources" +import * as adsTypes from '../src/generated/ads'; +import * as lrsTypes from '../src/generated/lrs'; +import { LoadStatsRequest__Output } from "../src/generated/envoy/service/load_stats/v3/LoadStatsRequest"; +import { LoadStatsResponse } from "../src/generated/envoy/service/load_stats/v3/LoadStatsResponse"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + 'envoy/service/discovery/v3/ads.proto', + 'envoy/service/load_stats/v3/lrs.proto', + 'envoy/config/listener/v3/listener.proto', + 'envoy/config/route/v3/route.proto', + 'envoy/config/cluster/v3/cluster.proto', + 'envoy/config/endpoint/v3/endpoint.proto', + 'envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/googleapis/', + __dirname + '/../../deps/protoc-gen-validate/', + ], + })) as unknown as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType; + +type AdsInputType = T extends EdsTypeUrl + ? ClusterLoadAssignment + : T extends CdsTypeUrl + ? Cluster + : T extends RdsTypeUrl + ? RouteConfiguration + : Listener; + +const ADS_TYPE_URLS = new Set([LDS_TYPE_URL, RDS_TYPE_URL, CDS_TYPE_URL, EDS_TYPE_URL]); + +interface ResponseState { + state: 'ACKED' | 'NACKED'; + errorMessage?: string; +} + +interface ResponseListener { + (typeUrl: AdsTypeUrl, responseState: ResponseState): void; +} + +type ResourceAny = AdsInputType & {'@type': T}; + +interface ResourceState { + resource?: ResourceAny; + resourceTypeVersion: number; + subscriptions: Set; +} + +interface ResourceTypeState { + resourceTypeVersion: number; + /** + * Key type is type URL + */ + resourceNameMap: Map>; +} + +interface ResourceMap { + [EDS_TYPE_URL]: ResourceTypeState; + [CDS_TYPE_URL]: ResourceTypeState; + [RDS_TYPE_URL]: ResourceTypeState; + [LDS_TYPE_URL]: ResourceTypeState; +} + +function isAdsTypeUrl(value: string): value is AdsTypeUrl { + return ADS_TYPE_URLS.has(value); +} + +export class XdsServer { + private resourceMap: ResourceMap = { + [EDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [CDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [RDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [LDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + }; + private responseListeners = new Set(); + private resourceTypesToIgnore = new Set(); + private clients = new Map>(); + private server: Server | null = null; + private port: number | null = null; + + addResponseListener(listener: ResponseListener) { + this.responseListeners.add(listener); + } + + removeResponseListener(listener: ResponseListener) { + this.responseListeners.delete(listener); + } + + setResource(resource: ResourceAny, name: string) { + const resourceTypeState = this.resourceMap[resource["@type"]] as ResourceTypeState; + resourceTypeState.resourceTypeVersion += 1; + let resourceState: ResourceState | undefined = resourceTypeState.resourceNameMap.get(name); + if (!resourceState) { + resourceState = { + resourceTypeVersion: 0, + subscriptions: new Set() + }; + resourceTypeState.resourceNameMap.set(name, resourceState); + } + resourceState.resourceTypeVersion = resourceTypeState.resourceTypeVersion; + resourceState.resource = resource; + this.sendResourceUpdates(resource['@type'], resourceState.subscriptions, new Set([name])); + } + + setLdsResource(resource: Listener) { + this.setResource({...resource, '@type': LDS_TYPE_URL}, resource.name!); + } + + setRdsResource(resource: RouteConfiguration) { + this.setResource({...resource, '@type': RDS_TYPE_URL}, resource.name!); + } + + setCdsResource(resource: Cluster) { + this.setResource({...resource, '@type': CDS_TYPE_URL}, resource.name!); + } + + setEdsResource(resource: ClusterLoadAssignment) { + this.setResource({...resource, '@type': EDS_TYPE_URL}, resource.cluster_name!); + } + + unsetResource(typeUrl: T, name: string) { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + resourceTypeState.resourceTypeVersion += 1; + let resourceState: ResourceState | undefined = resourceTypeState.resourceNameMap.get(name); + if (resourceState) { + resourceState.resourceTypeVersion = resourceTypeState.resourceTypeVersion; + delete resourceState.resource; + this.sendResourceUpdates(typeUrl, resourceState.subscriptions, new Set([name])); + } + } + + ignoreResourceType(typeUrl: AdsTypeUrl) { + this.resourceTypesToIgnore.add(typeUrl); + } + + private sendResourceUpdates(typeUrl: T, clients: Set, includeResources: Set) { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + const clientResources = new Map(); + for (const [resourceName, resourceState] of resourceTypeState.resourceNameMap) { + /* For RDS and EDS, only send updates for the listed updated resources. + * Otherwise include all resources. */ + if ((typeUrl === RDS_TYPE_URL || typeUrl === EDS_TYPE_URL) && !includeResources.has(resourceName)) { + continue; + } + if (!resourceState.resource) { + continue; + } + for (const clientName of clients) { + if (!resourceState.subscriptions.has(clientName)) { + continue; + } + let resourcesList = clientResources.get(clientName); + if (!resourcesList) { + resourcesList = []; + clientResources.set(clientName, resourcesList); + } + resourcesList.push(resourceState.resource); + } + } + for (const [clientName, resourceList] of clientResources) { + this.clients.get(clientName)?.write({ + resources: resourceList, + version_info: resourceTypeState.resourceTypeVersion.toString(), + nonce: resourceTypeState.resourceTypeVersion.toString(), + type_url: typeUrl + }); + } + } + + private updateResponseListeners(typeUrl: AdsTypeUrl, responseState: ResponseState) { + for (const listener of this.responseListeners) { + listener(typeUrl, responseState); + } + } + + private maybeSubscribe(typeUrl: T, client: string, resourceName: string): boolean { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + let resourceState = resourceTypeState.resourceNameMap.get(resourceName); + if (!resourceState) { + resourceState = { + resourceTypeVersion: 0, + subscriptions: new Set() + }; + resourceTypeState.resourceNameMap.set(resourceName, resourceState); + } + const newlySubscribed = !resourceState.subscriptions.has(client); + resourceState.subscriptions.add(client); + return newlySubscribed; + } + + private handleUnsubscriptions(typeUrl: AdsTypeUrl, client: string, requestedResourceNames?: Set) { + const resourceTypeState = this.resourceMap[typeUrl]; + for (const [resourceName, resourceState] of resourceTypeState.resourceNameMap) { + if (!requestedResourceNames || !requestedResourceNames.has(resourceName)) { + resourceState.subscriptions.delete(client); + if (!resourceState.resource && resourceState.subscriptions.size === 0) { + resourceTypeState.resourceNameMap.delete(resourceName) + } + } + } + } + + private handleRequest(clientName: string, request: DiscoveryRequest__Output) { + if (!isAdsTypeUrl(request.type_url)) { + console.error(`Received ADS request with unsupported type_url ${request.type_url}`); + return; + } + const clientResourceVersion = request.version_info === '' ? 0 : Number.parseInt(request.version_info); + if (request.error_detail) { + this.updateResponseListeners(request.type_url, {state: 'NACKED', errorMessage: request.error_detail.message}); + } else { + this.updateResponseListeners(request.type_url, {state: 'ACKED'}); + } + const requestedResourceNames = new Set(request.resource_names); + const resourceTypeState = this.resourceMap[request.type_url]; + const updatedResources = new Set(); + for (const resourceName of requestedResourceNames) { + if (this.maybeSubscribe(request.type_url, clientName, resourceName) || resourceTypeState.resourceNameMap.get(resourceName)!.resourceTypeVersion > clientResourceVersion) { + updatedResources.add(resourceName); + } + } + this.handleUnsubscriptions(request.type_url, clientName, requestedResourceNames); + if (updatedResources.size > 0) { + this.sendResourceUpdates(request.type_url, new Set([clientName]), updatedResources); + } + } + + StreamAggregatedResources(call: ServerDuplexStream) { + const clientName = call.getPeer(); + this.clients.set(clientName, call); + call.on('data', (request: DiscoveryRequest__Output) => { + this.handleRequest(clientName, request); + }); + call.on('end', () => { + this.clients.delete(clientName); + for (const typeUrl of ADS_TYPE_URLS) { + this.handleUnsubscriptions(typeUrl as AdsTypeUrl, clientName); + } + call.end(); + }); + } + + StreamLoadStats(call: ServerDuplexStream) { + const statsResponse = {load_reporting_interval: {seconds: 30}}; + call.write(statsResponse); + call.on('data', (request: LoadStatsRequest__Output) => { + call.write(statsResponse); + }); + call.on('end', () => { + call.end(); + }); + } + + startServer(callback: (error: Error | null, port: number) => void) { + if (this.server) { + return; + } + const server = new Server(); + server.addService(loadedProtos.envoy.service.discovery.v3.AggregatedDiscoveryService.service, this as unknown as UntypedServiceImplementation); + server.addService(loadedProtos.envoy.service.load_stats.v3.LoadReportingService.service, this as unknown as UntypedServiceImplementation); + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (error, port) => { + if (!error) { + this.server = server; + this.port = port; + server.start(); + } + callback(error, port); + }); + } + + shutdownServer() { + this.server?.forceShutdown(); + } + + getBootstrapInfoString(): string { + if (this.port === null) { + throw new Error('Bootstrap info unavailable; server not started'); + } + const bootstrapInfo = { + xds_servers: [{ + server_uri: `localhost:${this.port}`, + channel_creds: [{type: 'insecure'}] + }], + node: { + id: 'test', + locality: {} + } + } + return JSON.stringify(bootstrapInfo); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/tsconfig.json b/packages/grpc-js-xds/tsconfig.json new file mode 100644 index 000000000..c121a5f6d --- /dev/null +++ b/packages/grpc-js-xds/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "build", + "target": "es2017", + "lib": ["es2017"], + "module": "commonjs", + "incremental": true + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "interop/**/*.ts" + ] +} diff --git a/packages/grpc-js/.eslintrc b/packages/grpc-js/.eslintrc new file mode 100644 index 000000000..64585682c --- /dev/null +++ b/packages/grpc-js/.eslintrc @@ -0,0 +1,11 @@ +{ + "root": true, + "extends": "./node_modules/gts", + "rules": { + "node/no-unpublished-import": ["error", { + "tryExtensions": [".ts", ".js", ".json", ".node"] + }], + "@typescript-eslint/no-unused-vars": "off", + "node/no-unpublished-require": "off" + } +} diff --git a/packages/grpc-js/LICENSE b/packages/grpc-js/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/packages/grpc-js/LICENSE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md new file mode 100644 index 000000000..652ce5fef --- /dev/null +++ b/packages/grpc-js/README.md @@ -0,0 +1,77 @@ +# Pure JavaScript gRPC Client + +## Installation + +Node 12 is recommended. The exact set of compatible Node versions can be found in the `engines` field of the `package.json` file. + +```sh +npm install @grpc/grpc-js +``` + +## Documentation + +Documentation specifically for the `@grpc/grpc-js` package is currently not available. However, [documentation is available for the `grpc` package](https://grpc.github.io/grpc/node/grpc.html), and the two packages contain mostly the same interface. There are a few notable differences, however, and these differences are noted in the "Migrating from grpc" section below. + +## Features + +- Clients +- Automatic reconnection +- Servers +- Streaming +- Metadata +- Partial compression support: clients can decompress response messages +- Pick first and round robin load balancing policies +- Client Interceptors +- Connection Keepalives +- HTTP Connect support (proxies) + +If you need a feature from the `grpc` package that is not provided by the `@grpc/grpc-js`, please file a feature request with that information. + +This library does not directly handle `.proto` files. To use `.proto` files with this library we recommend using the `@grpc/proto-loader` package. + +## Migrating from [`grpc`](https://www.npmjs.com/package/grpc) + +`@grpc/grpc-js` is almost a drop-in replacement for `grpc`, but you may need to make a few code changes to use it: + +- If you are currently loading `.proto` files using `grpc.load`, that function is not available in this library. You should instead load your `.proto` files using `@grpc/proto-loader` and load the resulting package definition objects into `@grpc/grpc-js` using `grpc.loadPackageDefinition`. +- If you are currently loading packages generated by `grpc-tools`, you should instead generate your files using the `generate_package_definition` option in `grpc-tools`, then load the object exported by the generated file into `@grpc/grpc-js` using `grpc.loadPackageDefinition`. +- If you have a server and you are using `Server#bind` to bind ports, you will need to use `Server#bindAsync` instead. +- If you are using any channel options supported in `grpc` but not supported in `@grpc/grpc-js`, you may need to adjust your code to handle the different behavior. Refer to [the list of supported options](#supported-channel-options) below. +- Refer to the [detailed package comparison](https://github.com/grpc/grpc-node/blob/master/PACKAGE-COMPARISON.md) for more details on the differences between `grpc` and `@grpc/grpc-js`. + +## Supported Channel Options +Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. The channel arguments supported by `@grpc/grpc-js` are: + - `grpc.ssl_target_name_override` + - `grpc.primary_user_agent` + - `grpc.secondary_user_agent` + - `grpc.default_authority` + - `grpc.keepalive_time_ms` + - `grpc.keepalive_timeout_ms` + - `grpc.keepalive_permit_without_calls` + - `grpc.service_config` + - `grpc.max_concurrent_streams` + - `grpc.initial_reconnect_backoff_ms` + - `grpc.max_reconnect_backoff_ms` + - `grpc.use_local_subchannel_pool` + - `grpc.max_send_message_length` + - `grpc.max_receive_message_length` + - `grpc.enable_http_proxy` + - `grpc.default_compression_algorithm` + - `grpc.enable_channelz` + - `grpc.dns_min_time_between_resolutions_ms` + - `grpc.enable_retries` + - `grpc.per_rpc_retry_buffer_size` + - `grpc.retry_buffer_size` + - `grpc.service_config_disable_resolution` + - `grpc-node.max_session_memory` + - `channelOverride` + - `channelFactoryOverride` + +## Some Notes on API Guarantees + +The public API of this library follows semantic versioning, with some caveats: + +- Some methods are prefixed with an underscore. These methods are internal and should not be considered part of the public API. +- The class `Call` is only exposed due to limitations of TypeScript. It should not be considered part of the public API. +- In general, any API that is exposed by this library but is not exposed by the `grpc` library is likely an error and should not be considered part of the public API. +- The `grpc.experimental` namespace contains APIs that have not stabilized. Any API in that namespace may break in any minor version update. diff --git a/packages/grpc-js/gulpfile.ts b/packages/grpc-js/gulpfile.ts new file mode 100644 index 000000000..d85900364 --- /dev/null +++ b/packages/grpc-js/gulpfile.ts @@ -0,0 +1,85 @@ +/* + * Copyright 2017 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. + * + */ + +import * as gulp from 'gulp'; + +import * as mocha from 'gulp-mocha'; +import * as path from 'path'; +import * as execa from 'execa'; +import * as pify from 'pify'; +import * as semver from 'semver'; +import { ncp } from 'ncp'; + +const ncpP = pify(ncp); + +Error.stackTraceLimit = Infinity; + +const jsCoreDir = __dirname; +const outDir = path.resolve(jsCoreDir, 'build'); + +const pkgPath = path.resolve(jsCoreDir, 'package.json'); +const supportedVersionRange = require(pkgPath).engines.node; +const versionNotSupported = () => { + console.log(`Skipping grpc-js task for Node ${process.version}`); + return () => { return Promise.resolve(); }; +}; +const identity = (value: any): any => value; +const checkTask = semver.satisfies(process.version, supportedVersionRange) ? + identity : versionNotSupported; + +const execNpmVerb = (verb: string, ...args: string[]) => + execa('npm', [verb, ...args], {cwd: jsCoreDir, stdio: 'inherit'}); +const execNpmCommand = execNpmVerb.bind(null, 'run'); + +const install = checkTask(() => execNpmVerb('install', '--unsafe-perm')); + +/** + * Runs tslint on files in src/, with linting rules defined in tslint.json. + */ +const lint = checkTask(() => execNpmCommand('check')); + +const cleanFiles = checkTask(() => execNpmCommand('clean')); + +const clean = gulp.series(install, cleanFiles); + +const cleanAll = gulp.parallel(clean); + +/** + * Transpiles TypeScript files in src/ to JavaScript according to the settings + * found in tsconfig.json. + */ +const compile = checkTask(() => execNpmCommand('compile')); + +const copyTestFixtures = checkTask(() => ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`)); + +const runTests = checkTask(() => { + process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION = 'true'; + return gulp.src(`${outDir}/test/**/*.js`) + .pipe(mocha({reporter: 'mocha-jenkins-reporter', + require: ['ts-node/register']})); +}); + +const test = gulp.series(install, copyTestFixtures, runTests); + +export { + install, + lint, + clean, + cleanAll, + compile, + test +} diff --git a/packages/grpc-js/log.txt b/packages/grpc-js/log.txt new file mode 100644 index 000000000..7a6bbc2c8 --- /dev/null +++ b/packages/grpc-js/log.txt @@ -0,0 +1,971 @@ +{ + O: [Getter/Setter], + outDir: [Getter/Setter], + 'out-dir': [Getter/Setter], + _: [ + 'envoy/service/discovery/v2/ads.proto', + 'envoy/api/v2/listener.proto', + 'envoy/api/v2/route.proto', + 'envoy/api/v2/cluster.proto', + 'envoy/api/v2/endpoint.proto' + ], + keepCase: true, + 'keep-case': true, + longs: [Function: String], + enums: [Function: String], + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + 'deps/envoy-api/', + 'deps/udpa/', + 'node_modules/protobufjs/', + 'deps/googleapis/', + 'deps/protoc-gen-validate/' + ], + I: [ + 'deps/envoy-api/', + 'deps/udpa/', + 'node_modules/protobufjs/', + 'deps/googleapis/', + 'deps/protoc-gen-validate/' + ], + 'include-dirs': [ + 'deps/envoy-api/', + 'deps/udpa/', + 'node_modules/protobufjs/', + 'deps/googleapis/', + 'deps/protoc-gen-validate/' + ], + grpcLib: '../index', + 'grpc-lib': '../index', + '$0': 'node_modules/.bin/proto-loader-gen-types' +} +Processing envoy/service/discovery/v2/ads.proto +Writing src/generated//ads.d.ts +Writing src/generated//envoy/service/discovery/v2/AdsDummy.d.ts from file deps/envoy-api/envoy/service/discovery/v2/ads.proto +Writing src/generated//envoy/api/v2/DiscoveryRequest.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto +Writing src/generated//envoy/api/v2/DiscoveryResponse.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto +Writing src/generated//envoy/api/v2/DeltaDiscoveryRequest.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto +Writing src/generated//envoy/api/v2/DeltaDiscoveryResponse.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto +Writing src/generated//envoy/api/v2/Resource.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto +Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto +Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto +Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto +Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//google/protobuf/Any.d.ts from file null +Writing src/generated//google/protobuf/Duration.d.ts from file null +Writing src/generated//google/protobuf/Struct.d.ts from file null +Writing src/generated//google/protobuf/Value.d.ts from file null +Writing src/generated//google/protobuf/NullValue.d.ts from file null +Writing src/generated//google/protobuf/ListValue.d.ts from file null +Writing src/generated//google/protobuf/DoubleValue.d.ts from file null +Writing src/generated//google/protobuf/FloatValue.d.ts from file null +Writing src/generated//google/protobuf/Int64Value.d.ts from file null +Writing src/generated//google/protobuf/UInt64Value.d.ts from file null +Writing src/generated//google/protobuf/Int32Value.d.ts from file null +Writing src/generated//google/protobuf/UInt32Value.d.ts from file null +Writing src/generated//google/protobuf/BoolValue.d.ts from file null +Writing src/generated//google/protobuf/StringValue.d.ts from file null +Writing src/generated//google/protobuf/BytesValue.d.ts from file null +Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/Timestamp.d.ts from file null +Writing src/generated//google/rpc/Status.d.ts from file deps/googleapis/google/rpc/status.proto +Processing envoy/api/v2/listener.proto +Writing src/generated//listener.d.ts +Writing src/generated//envoy/api/v2/Listener.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto +Writing src/generated//envoy/api/v2/Listener/DrainType.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto +Writing src/generated//envoy/api/v2/Listener/DeprecatedV1.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto +Writing src/generated//envoy/api/v2/Listener/ConnectionBalanceConfig.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto +Writing src/generated//envoy/api/v2/Listener/ConnectionBalanceConfig/ExactBalance.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto +Writing src/generated//envoy/api/v2/listener/Filter.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto +Writing src/generated//envoy/api/v2/listener/FilterChainMatch.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto +Writing src/generated//envoy/api/v2/listener/FilterChainMatch/ConnectionSourceType.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto +Writing src/generated//envoy/api/v2/listener/FilterChain.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto +Writing src/generated//envoy/api/v2/listener/ListenerFilterChainMatchPredicate.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto +Writing src/generated//envoy/api/v2/listener/ListenerFilterChainMatchPredicate/MatchSet.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto +Writing src/generated//envoy/api/v2/listener/ListenerFilter.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto +Writing src/generated//envoy/api/v2/listener/UdpListenerConfig.d.ts from file deps/envoy-api/envoy/api/v2/listener/udp_listener_config.proto +Writing src/generated//envoy/api/v2/listener/ActiveRawUdpListenerConfig.d.ts from file deps/envoy-api/envoy/api/v2/listener/udp_listener_config.proto +Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto +Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto +Writing src/generated//envoy/api/v2/core/ApiVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ApiConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ApiConfigSource/ApiType.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/AggregatedConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/SelfConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/RateLimitSettings.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/GrpcService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/EnvoyGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/SslCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/GoogleLocalCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/ChannelCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/ServiceAccountJWTAccessCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/GoogleIAMCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/MetadataCredentialsFromPlugin.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/StsService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/auth/UpstreamTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto +Writing src/generated//envoy/api/v2/auth/DownstreamTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto +Writing src/generated//envoy/api/v2/auth/CommonTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto +Writing src/generated//envoy/api/v2/auth/CommonTlsContext/CombinedCertificateValidationContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto +Writing src/generated//envoy/api/v2/auth/GenericSecret.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto +Writing src/generated//envoy/api/v2/auth/SdsSecretConfig.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto +Writing src/generated//envoy/api/v2/auth/Secret.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto +Writing src/generated//envoy/api/v2/auth/TlsParameters.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/TlsParameters/TlsProtocol.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/PrivateKeyProvider.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/TlsCertificate.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/TlsSessionTicketKeys.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/CertificateValidationContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/CertificateValidationContext/TrustChainVerification.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/route/VirtualHost.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/VirtualHost/TlsRequirementType.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/FilterAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/Route.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/WeightedCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/WeightedCluster/ClusterWeight.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteMatch.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteMatch/GrpcRouteMatchOptions.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteMatch/TlsContextMatchOptions.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/CorsPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/ClusterNotFoundResponseCode.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/InternalRedirectAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/RequestMirrorPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/Header.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/Cookie.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/ConnectionProperties.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/QueryParameter.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/FilterState.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/UpgradeConfig.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryPriority.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryHostPredicate.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryBackOff.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/HedgePolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RedirectAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RedirectAction/RedirectResponseCode.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/DirectResponseAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/Decorator.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/Tracing.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/VirtualCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/SourceCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/DestinationCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/RequestHeaders.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/RemoteAddress.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/GenericKey.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/HeaderValueMatch.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/HeaderMatcher.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/QueryParameterMatcher.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/config/listener/v2/ApiListener.d.ts from file deps/envoy-api/envoy/config/listener/v2/api_listener.proto +Writing src/generated//envoy/config/filter/accesslog/v2/AccessLog.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/AccessLogFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/ComparisonFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/ComparisonFilter/Op.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/StatusCodeFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/DurationFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/NotHealthCheckFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/TraceableFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/RuntimeFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/AndFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/OrFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/HeaderFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/ResponseFlagFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/GrpcStatusFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/GrpcStatusFilter/Status.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/config/filter/accesslog/v2/ExtensionFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto +Writing src/generated//envoy/type/Int64Range.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/Int32Range.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/DoubleRange.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/matcher/RegexMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/RegexMatcher/GoogleRE2.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/RegexMatchAndSubstitute.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/StringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto +Writing src/generated//envoy/type/matcher/ListStringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag/Literal.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag/Environment.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag/Header.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag/Metadata.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKey.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKey/PathSegment.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind/Request.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind/Route.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind/Cluster.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind/Host.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//google/protobuf/Duration.d.ts from file null +Writing src/generated//google/protobuf/DoubleValue.d.ts from file null +Writing src/generated//google/protobuf/FloatValue.d.ts from file null +Writing src/generated//google/protobuf/Int64Value.d.ts from file null +Writing src/generated//google/protobuf/UInt64Value.d.ts from file null +Writing src/generated//google/protobuf/Int32Value.d.ts from file null +Writing src/generated//google/protobuf/UInt32Value.d.ts from file null +Writing src/generated//google/protobuf/BoolValue.d.ts from file null +Writing src/generated//google/protobuf/StringValue.d.ts from file null +Writing src/generated//google/protobuf/BytesValue.d.ts from file null +Writing src/generated//google/protobuf/Any.d.ts from file null +Writing src/generated//google/protobuf/Struct.d.ts from file null +Writing src/generated//google/protobuf/Value.d.ts from file null +Writing src/generated//google/protobuf/NullValue.d.ts from file null +Writing src/generated//google/protobuf/ListValue.d.ts from file null +Writing src/generated//google/protobuf/Timestamp.d.ts from file null +Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/Empty.d.ts from file null +Writing src/generated//google/api/Http.d.ts from file node_modules/protobufjs/google/api/http.proto +Writing src/generated//google/api/HttpRule.d.ts from file node_modules/protobufjs/google/api/http.proto +Writing src/generated//google/api/CustomHttpPattern.d.ts from file node_modules/protobufjs/google/api/http.proto +Processing envoy/api/v2/route.proto +Writing src/generated//route.d.ts +Writing src/generated//envoy/api/v2/RouteConfiguration.d.ts from file deps/envoy-api/envoy/api/v2/route.proto +Writing src/generated//envoy/api/v2/Vhds.d.ts from file deps/envoy-api/envoy/api/v2/route.proto +Writing src/generated//envoy/api/v2/core/ApiVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ApiConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ApiConfigSource/ApiType.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/AggregatedConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/SelfConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/RateLimitSettings.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/GrpcService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/EnvoyGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/SslCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/GoogleLocalCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/ChannelCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/ServiceAccountJWTAccessCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/GoogleIAMCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/MetadataCredentialsFromPlugin.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/StsService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto +Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto +Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/route/VirtualHost.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/VirtualHost/TlsRequirementType.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/FilterAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/Route.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/WeightedCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/WeightedCluster/ClusterWeight.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteMatch.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteMatch/GrpcRouteMatchOptions.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteMatch/TlsContextMatchOptions.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/CorsPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/ClusterNotFoundResponseCode.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/InternalRedirectAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/RequestMirrorPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/Header.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/Cookie.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/ConnectionProperties.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/QueryParameter.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/FilterState.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RouteAction/UpgradeConfig.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryPriority.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryHostPredicate.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryBackOff.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/HedgePolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RedirectAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RedirectAction/RedirectResponseCode.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/DirectResponseAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/Decorator.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/Tracing.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/VirtualCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/SourceCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/DestinationCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/RequestHeaders.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/RemoteAddress.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/GenericKey.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/RateLimit/Action/HeaderValueMatch.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/HeaderMatcher.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/api/v2/route/QueryParameterMatcher.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto +Writing src/generated//envoy/type/matcher/RegexMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/RegexMatcher/GoogleRE2.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/RegexMatchAndSubstitute.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/StringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto +Writing src/generated//envoy/type/matcher/ListStringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto +Writing src/generated//envoy/type/Int64Range.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/Int32Range.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/DoubleRange.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag/Literal.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag/Environment.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag/Header.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/tracing/v2/CustomTag/Metadata.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto +Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKey.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKey/PathSegment.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind/Request.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind/Route.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind/Cluster.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//envoy/type/metadata/v2/MetadataKind/Host.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto +Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//google/protobuf/DoubleValue.d.ts from file null +Writing src/generated//google/protobuf/FloatValue.d.ts from file null +Writing src/generated//google/protobuf/Int64Value.d.ts from file null +Writing src/generated//google/protobuf/UInt64Value.d.ts from file null +Writing src/generated//google/protobuf/Int32Value.d.ts from file null +Writing src/generated//google/protobuf/UInt32Value.d.ts from file null +Writing src/generated//google/protobuf/BoolValue.d.ts from file null +Writing src/generated//google/protobuf/StringValue.d.ts from file null +Writing src/generated//google/protobuf/BytesValue.d.ts from file null +Writing src/generated//google/protobuf/Duration.d.ts from file null +Writing src/generated//google/protobuf/Timestamp.d.ts from file null +Writing src/generated//google/protobuf/Any.d.ts from file null +Writing src/generated//google/protobuf/Struct.d.ts from file null +Writing src/generated//google/protobuf/Value.d.ts from file null +Writing src/generated//google/protobuf/NullValue.d.ts from file null +Writing src/generated//google/protobuf/ListValue.d.ts from file null +Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/Empty.d.ts from file null +Processing envoy/api/v2/cluster.proto +Writing src/generated//cluster.d.ts +Writing src/generated//envoy/api/v2/Cluster.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/DiscoveryType.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/LbPolicy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/DnsLookupFamily.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/ClusterProtocolSelection.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/TransportSocketMatch.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/CustomClusterType.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/EdsClusterConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/LbSubsetConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/LbSubsetConfig/LbSubsetFallbackPolicy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/LbSubsetConfig/LbSubsetSelector.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/LbSubsetConfig/LbSubsetSelector/LbSubsetSelectorFallbackPolicy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/LeastRequestLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/RingHashLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/RingHashLbConfig/HashFunction.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/OriginalDstLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/CommonLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/CommonLbConfig/ZoneAwareLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/CommonLbConfig/LocalityWeightedLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/CommonLbConfig/ConsistentHashingLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/Cluster/RefreshRate.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/LoadBalancingPolicy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/LoadBalancingPolicy/Policy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/UpstreamBindConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/UpstreamConnectionOptions.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto +Writing src/generated//envoy/api/v2/auth/UpstreamTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto +Writing src/generated//envoy/api/v2/auth/DownstreamTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto +Writing src/generated//envoy/api/v2/auth/CommonTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto +Writing src/generated//envoy/api/v2/auth/CommonTlsContext/CombinedCertificateValidationContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto +Writing src/generated//envoy/api/v2/auth/TlsParameters.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/TlsParameters/TlsProtocol.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/PrivateKeyProvider.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/TlsCertificate.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/TlsSessionTicketKeys.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/CertificateValidationContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/CertificateValidationContext/TrustChainVerification.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto +Writing src/generated//envoy/api/v2/auth/GenericSecret.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto +Writing src/generated//envoy/api/v2/auth/SdsSecretConfig.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto +Writing src/generated//envoy/api/v2/auth/Secret.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto +Writing src/generated//envoy/api/v2/core/ApiVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ApiConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ApiConfigSource/ApiType.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/AggregatedConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/SelfConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/RateLimitSettings.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/ConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto +Writing src/generated//envoy/api/v2/core/HealthStatus.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/Payload.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/HttpHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/TcpHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/RedisHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/GrpcHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/CustomHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/TlsOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/TcpProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/UpstreamHttpProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/HttpProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/HttpProtocolOptions/HeadersWithUnderscoresAction.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/Http1ProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/Http1ProtocolOptions/HeaderKeyFormat.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/Http1ProtocolOptions/HeaderKeyFormat/ProperCaseWords.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/Http2ProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/Http2ProtocolOptions/SettingsParameter.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/GrpcProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto +Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/GrpcService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/EnvoyGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/SslCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/GoogleLocalCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/ChannelCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/ServiceAccountJWTAccessCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/GoogleIAMCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/MetadataCredentialsFromPlugin.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/StsService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto +Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto +Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/EventServiceConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/event_service_config.proto +Writing src/generated//envoy/api/v2/cluster/CircuitBreakers.d.ts from file deps/envoy-api/envoy/api/v2/cluster/circuit_breaker.proto +Writing src/generated//envoy/api/v2/cluster/CircuitBreakers/Thresholds.d.ts from file deps/envoy-api/envoy/api/v2/cluster/circuit_breaker.proto +Writing src/generated//envoy/api/v2/cluster/CircuitBreakers/Thresholds/RetryBudget.d.ts from file deps/envoy-api/envoy/api/v2/cluster/circuit_breaker.proto +Writing src/generated//envoy/api/v2/cluster/Filter.d.ts from file deps/envoy-api/envoy/api/v2/cluster/filter.proto +Writing src/generated//envoy/api/v2/cluster/OutlierDetection.d.ts from file deps/envoy-api/envoy/api/v2/cluster/outlier_detection.proto +Writing src/generated//envoy/api/v2/ClusterLoadAssignment.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto +Writing src/generated//envoy/api/v2/ClusterLoadAssignment/Policy.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto +Writing src/generated//envoy/api/v2/ClusterLoadAssignment/Policy/DropOverload.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto +Writing src/generated//envoy/api/v2/endpoint/Endpoint.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +Writing src/generated//envoy/api/v2/endpoint/Endpoint/HealthCheckConfig.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +Writing src/generated//envoy/api/v2/endpoint/LbEndpoint.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +Writing src/generated//envoy/api/v2/endpoint/LocalityLbEndpoints.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/matcher/StringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto +Writing src/generated//envoy/type/matcher/ListStringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto +Writing src/generated//envoy/type/matcher/RegexMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/RegexMatcher/GoogleRE2.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/RegexMatchAndSubstitute.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/CodecClientType.d.ts from file deps/envoy-api/envoy/type/http.proto +Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto +Writing src/generated//envoy/type/Int64Range.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/Int32Range.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/DoubleRange.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//google/protobuf/Any.d.ts from file null +Writing src/generated//google/protobuf/Duration.d.ts from file null +Writing src/generated//google/protobuf/Struct.d.ts from file null +Writing src/generated//google/protobuf/Value.d.ts from file null +Writing src/generated//google/protobuf/NullValue.d.ts from file null +Writing src/generated//google/protobuf/ListValue.d.ts from file null +Writing src/generated//google/protobuf/DoubleValue.d.ts from file null +Writing src/generated//google/protobuf/FloatValue.d.ts from file null +Writing src/generated//google/protobuf/Int64Value.d.ts from file null +Writing src/generated//google/protobuf/UInt64Value.d.ts from file null +Writing src/generated//google/protobuf/Int32Value.d.ts from file null +Writing src/generated//google/protobuf/UInt32Value.d.ts from file null +Writing src/generated//google/protobuf/BoolValue.d.ts from file null +Writing src/generated//google/protobuf/StringValue.d.ts from file null +Writing src/generated//google/protobuf/BytesValue.d.ts from file null +Writing src/generated//google/protobuf/Timestamp.d.ts from file null +Writing src/generated//google/protobuf/Empty.d.ts from file null +Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/api/Http.d.ts from file node_modules/protobufjs/google/api/http.proto +Writing src/generated//google/api/HttpRule.d.ts from file node_modules/protobufjs/google/api/http.proto +Writing src/generated//google/api/CustomHttpPattern.d.ts from file node_modules/protobufjs/google/api/http.proto +Processing envoy/api/v2/endpoint.proto +Writing src/generated//endpoint.d.ts +Writing src/generated//envoy/api/v2/ClusterLoadAssignment.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto +Writing src/generated//envoy/api/v2/ClusterLoadAssignment/Policy.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto +Writing src/generated//envoy/api/v2/ClusterLoadAssignment/Policy/DropOverload.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto +Writing src/generated//envoy/api/v2/endpoint/Endpoint.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +Writing src/generated//envoy/api/v2/endpoint/Endpoint/HealthCheckConfig.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +Writing src/generated//envoy/api/v2/endpoint/LbEndpoint.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +Writing src/generated//envoy/api/v2/endpoint/LocalityLbEndpoints.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto +Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto +Writing src/generated//envoy/api/v2/core/HealthStatus.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/Payload.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/HttpHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/TcpHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/RedisHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/GrpcHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/CustomHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/HealthCheck/TlsOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto +Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto +Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto +Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto +Writing src/generated//envoy/api/v2/core/EventServiceConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/event_service_config.proto +Writing src/generated//envoy/api/v2/core/GrpcService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/EnvoyGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/SslCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/GoogleLocalCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/ChannelCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/ServiceAccountJWTAccessCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/GoogleIAMCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/MetadataCredentialsFromPlugin.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/StsService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto +Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto +Writing src/generated//envoy/type/matcher/StringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto +Writing src/generated//envoy/type/matcher/ListStringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto +Writing src/generated//envoy/type/matcher/RegexMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/RegexMatcher/GoogleRE2.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/matcher/RegexMatchAndSubstitute.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto +Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto +Writing src/generated//envoy/type/CodecClientType.d.ts from file deps/envoy-api/envoy/type/http.proto +Writing src/generated//envoy/type/Int64Range.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/Int32Range.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//envoy/type/DoubleRange.d.ts from file deps/envoy-api/envoy/type/range.proto +Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto +Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto +Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto +Writing src/generated//google/protobuf/Duration.d.ts from file null +Writing src/generated//google/protobuf/DoubleValue.d.ts from file null +Writing src/generated//google/protobuf/FloatValue.d.ts from file null +Writing src/generated//google/protobuf/Int64Value.d.ts from file null +Writing src/generated//google/protobuf/UInt64Value.d.ts from file null +Writing src/generated//google/protobuf/Int32Value.d.ts from file null +Writing src/generated//google/protobuf/UInt32Value.d.ts from file null +Writing src/generated//google/protobuf/BoolValue.d.ts from file null +Writing src/generated//google/protobuf/StringValue.d.ts from file null +Writing src/generated//google/protobuf/BytesValue.d.ts from file null +Writing src/generated//google/protobuf/Timestamp.d.ts from file null +Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto +Writing src/generated//google/protobuf/Any.d.ts from file null +Writing src/generated//google/protobuf/Struct.d.ts from file null +Writing src/generated//google/protobuf/Value.d.ts from file null +Writing src/generated//google/protobuf/NullValue.d.ts from file null +Writing src/generated//google/protobuf/ListValue.d.ts from file null +Writing src/generated//google/protobuf/Empty.d.ts from file null +Writing src/generated//google/api/Http.d.ts from file node_modules/protobufjs/google/api/http.proto +Writing src/generated//google/api/HttpRule.d.ts from file node_modules/protobufjs/google/api/http.proto +Writing src/generated//google/api/CustomHttpPattern.d.ts from file node_modules/protobufjs/google/api/http.proto +Success diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json new file mode 100644 index 000000000..19e37d19b --- /dev/null +++ b/packages/grpc-js/package.json @@ -0,0 +1,78 @@ +{ + "name": "@grpc/grpc-js", + "version": "1.8.22", + "description": "gRPC Library for Node - pure JS implementation", + "homepage": "https://grpc.io/", + "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", + "main": "build/src/index.js", + "engines": { + "node": "^8.13.0 || >=10.10.0" + }, + "keywords": [], + "author": { + "name": "Google Inc." + }, + "types": "build/src/index.d.ts", + "license": "Apache-2.0", + "devDependencies": { + "@types/gulp": "^4.0.6", + "@types/gulp-mocha": "0.0.32", + "@types/lodash": "^4.14.186", + "@types/mocha": "^5.2.6", + "@types/ncp": "^2.0.1", + "@types/pify": "^3.0.2", + "@types/semver": "^7.3.9", + "clang-format": "^1.0.55", + "execa": "^2.0.3", + "gts": "^3.1.1", + "gulp": "^4.0.2", + "gulp-mocha": "^6.0.0", + "lodash": "^4.17.4", + "madge": "^5.0.1", + "mocha-jenkins-reporter": "^0.4.1", + "ncp": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ts-node": "^8.3.0", + "typescript": "^4.8.4" + }, + "contributors": [ + { + "name": "Google Inc." + } + ], + "scripts": { + "build": "npm run compile", + "clean": "rimraf ./build", + "compile": "tsc -p .", + "format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts", + "lint": "npm run check", + "prepare": "npm run generate-types && npm run compile", + "test": "gulp test", + "check": "gts check src/**/*.ts", + "fix": "gts fix src/*.ts", + "pretest": "npm run generate-types && npm run generate-test-types && npm run compile", + "posttest": "npm run check && madge -c ./build/src", + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ --include-dirs test/fixtures/ -O src/generated/ --grpcLib ../index channelz.proto", + "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --include-dirs test/fixtures/ -O test/generated/ --grpcLib ../../src/index test_service.proto" + }, + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "files": [ + "src/**/*.ts", + "build/src/**/*.{js,d.ts,js.map}", + "proto/*.proto", + "LICENSE", + "deps/envoy-api/envoy/api/v2/**/*.proto", + "deps/envoy-api/envoy/config/**/*.proto", + "deps/envoy-api/envoy/service/**/*.proto", + "deps/envoy-api/envoy/type/**/*.proto", + "deps/udpa/udpa/**/*.proto", + "deps/googleapis/google/api/*.proto", + "deps/googleapis/google/rpc/*.proto", + "deps/protoc-gen-validate/validate/**/*.proto" + ] +} diff --git a/packages/grpc-js/prettier.config.js b/packages/grpc-js/prettier.config.js new file mode 100644 index 000000000..92747c8cc --- /dev/null +++ b/packages/grpc-js/prettier.config.js @@ -0,0 +1,5 @@ +module.exports = { + proseWrap: 'always', + singleQuote: true, + trailingComma: 'es5', +}; diff --git a/packages/grpc-js/proto/channelz.proto b/packages/grpc-js/proto/channelz.proto new file mode 100644 index 000000000..446e9794b --- /dev/null +++ b/packages/grpc-js/proto/channelz.proto @@ -0,0 +1,564 @@ +// Copyright 2018 The 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. + +// This file defines an interface for exporting monitoring information +// out of gRPC servers. See the full design at +// https://github.com/grpc/proposal/blob/master/A14-channelz.md +// +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/channelz/v1/channelz.proto + +syntax = "proto3"; + +package grpc.channelz.v1; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option go_package = "google.golang.org/grpc/channelz/grpc_channelz_v1"; +option java_multiple_files = true; +option java_package = "io.grpc.channelz.v1"; +option java_outer_classname = "ChannelzProto"; + +// Channel is a logical grouping of channels, subchannels, and sockets. +message Channel { + // The identifier for this channel. This should bet set. + ChannelRef ref = 1; + // Data specific to this channel. + ChannelData data = 2; + // At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + + // There are no ordering guarantees on the order of channel refs. + // There may not be cycles in the ref graph. + // A channel ref may be present in more than one channel or subchannel. + repeated ChannelRef channel_ref = 3; + + // At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + // There are no ordering guarantees on the order of subchannel refs. + // There may not be cycles in the ref graph. + // A sub channel ref may be present in more than one channel or subchannel. + repeated SubchannelRef subchannel_ref = 4; + + // There are no ordering guarantees on the order of sockets. + repeated SocketRef socket_ref = 5; +} + +// Subchannel is a logical grouping of channels, subchannels, and sockets. +// A subchannel is load balanced over by it's ancestor +message Subchannel { + // The identifier for this channel. + SubchannelRef ref = 1; + // Data specific to this channel. + ChannelData data = 2; + // At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + + // There are no ordering guarantees on the order of channel refs. + // There may not be cycles in the ref graph. + // A channel ref may be present in more than one channel or subchannel. + repeated ChannelRef channel_ref = 3; + + // At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + // There are no ordering guarantees on the order of subchannel refs. + // There may not be cycles in the ref graph. + // A sub channel ref may be present in more than one channel or subchannel. + repeated SubchannelRef subchannel_ref = 4; + + // There are no ordering guarantees on the order of sockets. + repeated SocketRef socket_ref = 5; +} + +// These come from the specified states in this document: +// https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md +message ChannelConnectivityState { + enum State { + UNKNOWN = 0; + IDLE = 1; + CONNECTING = 2; + READY = 3; + TRANSIENT_FAILURE = 4; + SHUTDOWN = 5; + } + State state = 1; +} + +// Channel data is data related to a specific Channel or Subchannel. +message ChannelData { + // The connectivity state of the channel or subchannel. Implementations + // should always set this. + ChannelConnectivityState state = 1; + + // The target this channel originally tried to connect to. May be absent + string target = 2; + + // A trace of recent events on the channel. May be absent. + ChannelTrace trace = 3; + + // The number of calls started on the channel + int64 calls_started = 4; + // The number of calls that have completed with an OK status + int64 calls_succeeded = 5; + // The number of calls that have completed with a non-OK status + int64 calls_failed = 6; + + // The last time a call was started on the channel. + google.protobuf.Timestamp last_call_started_timestamp = 7; +} + +// A trace event is an interesting thing that happened to a channel or +// subchannel, such as creation, address resolution, subchannel creation, etc. +message ChannelTraceEvent { + // High level description of the event. + string description = 1; + // The supported severity levels of trace events. + enum Severity { + CT_UNKNOWN = 0; + CT_INFO = 1; + CT_WARNING = 2; + CT_ERROR = 3; + } + // the severity of the trace event + Severity severity = 2; + // When this event occurred. + google.protobuf.Timestamp timestamp = 3; + // ref of referenced channel or subchannel. + // Optional, only present if this event refers to a child object. For example, + // this field would be filled if this trace event was for a subchannel being + // created. + oneof child_ref { + ChannelRef channel_ref = 4; + SubchannelRef subchannel_ref = 5; + } +} + +// ChannelTrace represents the recent events that have occurred on the channel. +message ChannelTrace { + // Number of events ever logged in this tracing object. This can differ from + // events.size() because events can be overwritten or garbage collected by + // implementations. + int64 num_events_logged = 1; + // Time that this channel was created. + google.protobuf.Timestamp creation_timestamp = 2; + // List of events that have occurred on this channel. + repeated ChannelTraceEvent events = 3; +} + +// ChannelRef is a reference to a Channel. +message ChannelRef { + // The globally unique id for this channel. Must be a positive number. + int64 channel_id = 1; + // An optional name associated with the channel. + string name = 2; + // Intentionally don't use field numbers from other refs. + reserved 3, 4, 5, 6, 7, 8; +} + +// SubchannelRef is a reference to a Subchannel. +message SubchannelRef { + // The globally unique id for this subchannel. Must be a positive number. + int64 subchannel_id = 7; + // An optional name associated with the subchannel. + string name = 8; + // Intentionally don't use field numbers from other refs. + reserved 1, 2, 3, 4, 5, 6; +} + +// SocketRef is a reference to a Socket. +message SocketRef { + // The globally unique id for this socket. Must be a positive number. + int64 socket_id = 3; + // An optional name associated with the socket. + string name = 4; + // Intentionally don't use field numbers from other refs. + reserved 1, 2, 5, 6, 7, 8; +} + +// ServerRef is a reference to a Server. +message ServerRef { + // A globally unique identifier for this server. Must be a positive number. + int64 server_id = 5; + // An optional name associated with the server. + string name = 6; + // Intentionally don't use field numbers from other refs. + reserved 1, 2, 3, 4, 7, 8; +} + +// Server represents a single server. There may be multiple servers in a single +// program. +message Server { + // The identifier for a Server. This should be set. + ServerRef ref = 1; + // The associated data of the Server. + ServerData data = 2; + + // The sockets that the server is listening on. There are no ordering + // guarantees. This may be absent. + repeated SocketRef listen_socket = 3; +} + +// ServerData is data for a specific Server. +message ServerData { + // A trace of recent events on the server. May be absent. + ChannelTrace trace = 1; + + // The number of incoming calls started on the server + int64 calls_started = 2; + // The number of incoming calls that have completed with an OK status + int64 calls_succeeded = 3; + // The number of incoming calls that have a completed with a non-OK status + int64 calls_failed = 4; + + // The last time a call was started on the server. + google.protobuf.Timestamp last_call_started_timestamp = 5; +} + +// Information about an actual connection. Pronounced "sock-ay". +message Socket { + // The identifier for the Socket. + SocketRef ref = 1; + + // Data specific to this Socket. + SocketData data = 2; + // The locally bound address. + Address local = 3; + // The remote bound address. May be absent. + Address remote = 4; + // Security details for this socket. May be absent if not available, or + // there is no security on the socket. + Security security = 5; + + // Optional, represents the name of the remote endpoint, if different than + // the original target name. + string remote_name = 6; +} + +// SocketData is data associated for a specific Socket. The fields present +// are specific to the implementation, so there may be minor differences in +// the semantics. (e.g. flow control windows) +message SocketData { + // The number of streams that have been started. + int64 streams_started = 1; + // The number of streams that have ended successfully: + // On client side, received frame with eos bit set; + // On server side, sent frame with eos bit set. + int64 streams_succeeded = 2; + // The number of streams that have ended unsuccessfully: + // On client side, ended without receiving frame with eos bit set; + // On server side, ended without sending frame with eos bit set. + int64 streams_failed = 3; + // The number of grpc messages successfully sent on this socket. + int64 messages_sent = 4; + // The number of grpc messages received on this socket. + int64 messages_received = 5; + + // The number of keep alives sent. This is typically implemented with HTTP/2 + // ping messages. + int64 keep_alives_sent = 6; + + // The last time a stream was created by this endpoint. Usually unset for + // servers. + google.protobuf.Timestamp last_local_stream_created_timestamp = 7; + // The last time a stream was created by the remote endpoint. Usually unset + // for clients. + google.protobuf.Timestamp last_remote_stream_created_timestamp = 8; + + // The last time a message was sent by this endpoint. + google.protobuf.Timestamp last_message_sent_timestamp = 9; + // The last time a message was received by this endpoint. + google.protobuf.Timestamp last_message_received_timestamp = 10; + + // The amount of window, granted to the local endpoint by the remote endpoint. + // This may be slightly out of date due to network latency. This does NOT + // include stream level or TCP level flow control info. + google.protobuf.Int64Value local_flow_control_window = 11; + + // The amount of window, granted to the remote endpoint by the local endpoint. + // This may be slightly out of date due to network latency. This does NOT + // include stream level or TCP level flow control info. + google.protobuf.Int64Value remote_flow_control_window = 12; + + // Socket options set on this socket. May be absent if 'summary' is set + // on GetSocketRequest. + repeated SocketOption option = 13; +} + +// Address represents the address used to create the socket. +message Address { + message TcpIpAddress { + // Either the IPv4 or IPv6 address in bytes. Will be either 4 bytes or 16 + // bytes in length. + bytes ip_address = 1; + // 0-64k, or -1 if not appropriate. + int32 port = 2; + } + // A Unix Domain Socket address. + message UdsAddress { + string filename = 1; + } + // An address type not included above. + message OtherAddress { + // The human readable version of the value. This value should be set. + string name = 1; + // The actual address message. + google.protobuf.Any value = 2; + } + + oneof address { + TcpIpAddress tcpip_address = 1; + UdsAddress uds_address = 2; + OtherAddress other_address = 3; + } +} + +// Security represents details about how secure the socket is. +message Security { + message Tls { + oneof cipher_suite { + // The cipher suite name in the RFC 4346 format: + // https://tools.ietf.org/html/rfc4346#appendix-C + string standard_name = 1; + // Some other way to describe the cipher suite if + // the RFC 4346 name is not available. + string other_name = 2; + } + // the certificate used by this endpoint. + bytes local_certificate = 3; + // the certificate used by the remote endpoint. + bytes remote_certificate = 4; + } + message OtherSecurity { + // The human readable version of the value. + string name = 1; + // The actual security details message. + google.protobuf.Any value = 2; + } + oneof model { + Tls tls = 1; + OtherSecurity other = 2; + } +} + +// SocketOption represents socket options for a socket. Specifically, these +// are the options returned by getsockopt(). +message SocketOption { + // The full name of the socket option. Typically this will be the upper case + // name, such as "SO_REUSEPORT". + string name = 1; + // The human readable value of this socket option. At least one of value or + // additional will be set. + string value = 2; + // Additional data associated with the socket option. At least one of value + // or additional will be set. + google.protobuf.Any additional = 3; +} + +// For use with SocketOption's additional field. This is primarily used for +// SO_RCVTIMEO and SO_SNDTIMEO +message SocketOptionTimeout { + google.protobuf.Duration duration = 1; +} + +// For use with SocketOption's additional field. This is primarily used for +// SO_LINGER. +message SocketOptionLinger { + // active maps to `struct linger.l_onoff` + bool active = 1; + // duration maps to `struct linger.l_linger` + google.protobuf.Duration duration = 2; +} + +// For use with SocketOption's additional field. Tcp info for +// SOL_TCP and TCP_INFO. +message SocketOptionTcpInfo { + uint32 tcpi_state = 1; + + uint32 tcpi_ca_state = 2; + uint32 tcpi_retransmits = 3; + uint32 tcpi_probes = 4; + uint32 tcpi_backoff = 5; + uint32 tcpi_options = 6; + uint32 tcpi_snd_wscale = 7; + uint32 tcpi_rcv_wscale = 8; + + uint32 tcpi_rto = 9; + uint32 tcpi_ato = 10; + uint32 tcpi_snd_mss = 11; + uint32 tcpi_rcv_mss = 12; + + uint32 tcpi_unacked = 13; + uint32 tcpi_sacked = 14; + uint32 tcpi_lost = 15; + uint32 tcpi_retrans = 16; + uint32 tcpi_fackets = 17; + + uint32 tcpi_last_data_sent = 18; + uint32 tcpi_last_ack_sent = 19; + uint32 tcpi_last_data_recv = 20; + uint32 tcpi_last_ack_recv = 21; + + uint32 tcpi_pmtu = 22; + uint32 tcpi_rcv_ssthresh = 23; + uint32 tcpi_rtt = 24; + uint32 tcpi_rttvar = 25; + uint32 tcpi_snd_ssthresh = 26; + uint32 tcpi_snd_cwnd = 27; + uint32 tcpi_advmss = 28; + uint32 tcpi_reordering = 29; +} + +// Channelz is a service exposed by gRPC servers that provides detailed debug +// information. +service Channelz { + // Gets all root channels (i.e. channels the application has directly + // created). This does not include subchannels nor non-top level channels. + rpc GetTopChannels(GetTopChannelsRequest) returns (GetTopChannelsResponse); + // Gets all servers that exist in the process. + rpc GetServers(GetServersRequest) returns (GetServersResponse); + // Returns a single Server, or else a NOT_FOUND code. + rpc GetServer(GetServerRequest) returns (GetServerResponse); + // Gets all server sockets that exist in the process. + rpc GetServerSockets(GetServerSocketsRequest) returns (GetServerSocketsResponse); + // Returns a single Channel, or else a NOT_FOUND code. + rpc GetChannel(GetChannelRequest) returns (GetChannelResponse); + // Returns a single Subchannel, or else a NOT_FOUND code. + rpc GetSubchannel(GetSubchannelRequest) returns (GetSubchannelResponse); + // Returns a single Socket or else a NOT_FOUND code. + rpc GetSocket(GetSocketRequest) returns (GetSocketResponse); +} + +message GetTopChannelsRequest { + // start_channel_id indicates that only channels at or above this id should be + // included in the results. + // To request the first page, this should be set to 0. To request + // subsequent pages, the client generates this value by adding 1 to + // the highest seen result ID. + int64 start_channel_id = 1; + + // If non-zero, the server will return a page of results containing + // at most this many items. If zero, the server will choose a + // reasonable page size. Must never be negative. + int64 max_results = 2; +} + +message GetTopChannelsResponse { + // list of channels that the connection detail service knows about. Sorted in + // ascending channel_id order. + // Must contain at least 1 result, otherwise 'end' must be true. + repeated Channel channel = 1; + // If set, indicates that the list of channels is the final list. Requesting + // more channels can only return more if they are created after this RPC + // completes. + bool end = 2; +} + +message GetServersRequest { + // start_server_id indicates that only servers at or above this id should be + // included in the results. + // To request the first page, this must be set to 0. To request + // subsequent pages, the client generates this value by adding 1 to + // the highest seen result ID. + int64 start_server_id = 1; + + // If non-zero, the server will return a page of results containing + // at most this many items. If zero, the server will choose a + // reasonable page size. Must never be negative. + int64 max_results = 2; +} + +message GetServersResponse { + // list of servers that the connection detail service knows about. Sorted in + // ascending server_id order. + // Must contain at least 1 result, otherwise 'end' must be true. + repeated Server server = 1; + // If set, indicates that the list of servers is the final list. Requesting + // more servers will only return more if they are created after this RPC + // completes. + bool end = 2; +} + +message GetServerRequest { + // server_id is the identifier of the specific server to get. + int64 server_id = 1; +} + +message GetServerResponse { + // The Server that corresponds to the requested server_id. This field + // should be set. + Server server = 1; +} + +message GetServerSocketsRequest { + int64 server_id = 1; + // start_socket_id indicates that only sockets at or above this id should be + // included in the results. + // To request the first page, this must be set to 0. To request + // subsequent pages, the client generates this value by adding 1 to + // the highest seen result ID. + int64 start_socket_id = 2; + + // If non-zero, the server will return a page of results containing + // at most this many items. If zero, the server will choose a + // reasonable page size. Must never be negative. + int64 max_results = 3; +} + +message GetServerSocketsResponse { + // list of socket refs that the connection detail service knows about. Sorted in + // ascending socket_id order. + // Must contain at least 1 result, otherwise 'end' must be true. + repeated SocketRef socket_ref = 1; + // If set, indicates that the list of sockets is the final list. Requesting + // more sockets will only return more if they are created after this RPC + // completes. + bool end = 2; +} + +message GetChannelRequest { + // channel_id is the identifier of the specific channel to get. + int64 channel_id = 1; +} + +message GetChannelResponse { + // The Channel that corresponds to the requested channel_id. This field + // should be set. + Channel channel = 1; +} + +message GetSubchannelRequest { + // subchannel_id is the identifier of the specific subchannel to get. + int64 subchannel_id = 1; +} + +message GetSubchannelResponse { + // The Subchannel that corresponds to the requested subchannel_id. This + // field should be set. + Subchannel subchannel = 1; +} + +message GetSocketRequest { + // socket_id is the identifier of the specific socket to get. + int64 socket_id = 1; + + // If true, the response will contain only high level information + // that is inexpensive to obtain. Fields thay may be omitted are + // documented. + bool summary = 2; +} + +message GetSocketResponse { + // The Socket that corresponds to the requested socket_id. This field + // should be set. + Socket socket = 1; +} \ No newline at end of file diff --git a/packages/grpc-js/src/admin.ts b/packages/grpc-js/src/admin.ts new file mode 100644 index 000000000..7745d07d8 --- /dev/null +++ b/packages/grpc-js/src/admin.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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. + * + */ + +import { ServiceDefinition } from "./make-client"; +import { Server, UntypedServiceImplementation } from "./server"; + +interface GetServiceDefinition { + (): ServiceDefinition; +} + +interface GetHandlers { + (): UntypedServiceImplementation; +} + +const registeredAdminServices: {getServiceDefinition: GetServiceDefinition, getHandlers: GetHandlers}[] = []; + +export function registerAdminService(getServiceDefinition: GetServiceDefinition, getHandlers: GetHandlers) { + registeredAdminServices.push({getServiceDefinition, getHandlers}); +} + +export function addAdminServicesToServer(server: Server): void { + for (const {getServiceDefinition, getHandlers} of registeredAdminServices) { + server.addService(getServiceDefinition(), getHandlers()); + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/backoff-timeout.ts b/packages/grpc-js/src/backoff-timeout.ts new file mode 100644 index 000000000..3ffd26064 --- /dev/null +++ b/packages/grpc-js/src/backoff-timeout.ts @@ -0,0 +1,181 @@ +/* + * Copyright 2019 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. + * + */ + +const INITIAL_BACKOFF_MS = 1000; +const BACKOFF_MULTIPLIER = 1.6; +const MAX_BACKOFF_MS = 120000; +const BACKOFF_JITTER = 0.2; + +/** + * Get a number uniformly at random in the range [min, max) + * @param min + * @param max + */ +function uniformRandom(min: number, max: number) { + return Math.random() * (max - min) + min; +} + +export interface BackoffOptions { + initialDelay?: number; + multiplier?: number; + jitter?: number; + maxDelay?: number; +} + +export class BackoffTimeout { + /** + * The delay time at the start, and after each reset. + */ + private readonly initialDelay: number = INITIAL_BACKOFF_MS; + /** + * The exponential backoff multiplier. + */ + private readonly multiplier: number = BACKOFF_MULTIPLIER; + /** + * The maximum delay time + */ + private readonly maxDelay: number = MAX_BACKOFF_MS; + /** + * The maximum fraction by which the delay time can randomly vary after + * applying the multiplier. + */ + private readonly jitter: number = BACKOFF_JITTER; + /** + * The delay time for the next time the timer runs. + */ + private nextDelay: number; + /** + * The handle of the underlying timer. If running is false, this value refers + * to an object representing a timer that has ended, but it can still be + * interacted with without error. + */ + private timerId: NodeJS.Timeout; + /** + * Indicates whether the timer is currently running. + */ + private running = false; + /** + * Indicates whether the timer should keep the Node process running if no + * other async operation is doing so. + */ + private hasRef = true; + /** + * The time that the currently running timer was started. Only valid if + * running is true. + */ + private startTime: Date = new Date(); + + constructor(private callback: () => void, options?: BackoffOptions) { + if (options) { + if (options.initialDelay) { + this.initialDelay = options.initialDelay; + } + if (options.multiplier) { + this.multiplier = options.multiplier; + } + if (options.jitter) { + this.jitter = options.jitter; + } + if (options.maxDelay) { + this.maxDelay = options.maxDelay; + } + } + this.nextDelay = this.initialDelay; + this.timerId = setTimeout(() => {}, 0); + clearTimeout(this.timerId); + } + + private runTimer(delay: number) { + clearTimeout(this.timerId); + this.timerId = setTimeout(() => { + this.callback(); + this.running = false; + }, delay); + if (!this.hasRef) { + this.timerId.unref?.(); + } + } + + /** + * Call the callback after the current amount of delay time + */ + runOnce() { + this.running = true; + this.startTime = new Date(); + this.runTimer(this.nextDelay); + const nextBackoff = Math.min( + this.nextDelay * this.multiplier, + this.maxDelay + ); + const jitterMagnitude = nextBackoff * this.jitter; + this.nextDelay = + nextBackoff + uniformRandom(-jitterMagnitude, jitterMagnitude); + } + + /** + * Stop the timer. The callback will not be called until `runOnce` is called + * again. + */ + stop() { + clearTimeout(this.timerId); + this.running = false; + } + + /** + * Reset the delay time to its initial value. If the timer is still running, + * retroactively apply that reset to the current timer. + */ + reset() { + this.nextDelay = this.initialDelay; + if (this.running) { + const now = new Date(); + const newEndTime = this.startTime; + newEndTime.setMilliseconds(newEndTime.getMilliseconds() + this.nextDelay); + clearTimeout(this.timerId); + if (now < newEndTime) { + this.runTimer(newEndTime.getTime() - now.getTime()); + } else { + this.running = false; + } + } + } + + /** + * Check whether the timer is currently running. + */ + isRunning() { + return this.running; + } + + /** + * Set that while the timer is running, it should keep the Node process + * running. + */ + ref() { + this.hasRef = true; + this.timerId.ref?.(); + } + + /** + * Set that while the timer is running, it should not keep the Node process + * running. + */ + unref() { + this.hasRef = false; + this.timerId.unref?.(); + } +} diff --git a/packages/grpc-js/src/call-credentials.ts b/packages/grpc-js/src/call-credentials.ts new file mode 100644 index 000000000..b98624ee2 --- /dev/null +++ b/packages/grpc-js/src/call-credentials.ts @@ -0,0 +1,226 @@ +/* + * Copyright 2019 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. + * + */ + +import { Metadata } from './metadata'; + +export interface CallMetadataOptions { + service_url: string; +} + +export type CallMetadataGenerator = ( + options: CallMetadataOptions, + cb: (err: Error | null, metadata?: Metadata) => void +) => void; + +// google-auth-library pre-v2.0.0 does not have getRequestHeaders +// but has getRequestMetadata, which is deprecated in v2.0.0 +export interface OldOAuth2Client { + getRequestMetadata: ( + url: string, + callback: ( + err: Error | null, + headers?: { + [index: string]: string; + } + ) => void + ) => void; +} + +export interface CurrentOAuth2Client { + getRequestHeaders: (url?: string) => Promise<{ [index: string]: string }>; +} + +export type OAuth2Client = OldOAuth2Client | CurrentOAuth2Client; + +function isCurrentOauth2Client( + client: OAuth2Client +): client is CurrentOAuth2Client { + return ( + 'getRequestHeaders' in client && + typeof client.getRequestHeaders === 'function' + ); +} + +/** + * A class that represents a generic method of adding authentication-related + * metadata on a per-request basis. + */ +export abstract class CallCredentials { + /** + * Asynchronously generates a new Metadata object. + * @param options Options used in generating the Metadata object. + */ + abstract generateMetadata(options: CallMetadataOptions): Promise; + /** + * Creates a new CallCredentials object from properties of both this and + * another CallCredentials object. This object's metadata generator will be + * called first. + * @param callCredentials The other CallCredentials object. + */ + abstract compose(callCredentials: CallCredentials): CallCredentials; + + /** + * Check whether two call credentials objects are equal. Separate + * SingleCallCredentials with identical metadata generator functions are + * equal. + * @param other The other CallCredentials object to compare with. + */ + abstract _equals(other: CallCredentials): boolean; + + /** + * Creates a new CallCredentials object from a given function that generates + * Metadata objects. + * @param metadataGenerator A function that accepts a set of options, and + * generates a Metadata object based on these options, which is passed back + * to the caller via a supplied (err, metadata) callback. + */ + static createFromMetadataGenerator( + metadataGenerator: CallMetadataGenerator + ): CallCredentials { + return new SingleCallCredentials(metadataGenerator); + } + + /** + * Create a gRPC credential from a Google credential object. + * @param googleCredentials The authentication client to use. + * @return The resulting CallCredentials object. + */ + static createFromGoogleCredential( + googleCredentials: OAuth2Client + ): CallCredentials { + return CallCredentials.createFromMetadataGenerator((options, callback) => { + let getHeaders: Promise<{ [index: string]: string }>; + if (isCurrentOauth2Client(googleCredentials)) { + getHeaders = googleCredentials.getRequestHeaders(options.service_url); + } else { + getHeaders = new Promise((resolve, reject) => { + googleCredentials.getRequestMetadata( + options.service_url, + (err, headers) => { + if (err) { + reject(err); + return; + } + if (!headers) { + reject(new Error('Headers not set by metadata plugin')); + return; + } + resolve(headers); + } + ); + }); + } + getHeaders.then( + (headers) => { + const metadata = new Metadata(); + for (const key of Object.keys(headers)) { + metadata.add(key, headers[key]); + } + callback(null, metadata); + }, + (err) => { + callback(err); + } + ); + }); + } + + static createEmpty(): CallCredentials { + return new EmptyCallCredentials(); + } +} + +class ComposedCallCredentials extends CallCredentials { + constructor(private creds: CallCredentials[]) { + super(); + } + + async generateMetadata(options: CallMetadataOptions): Promise { + const base: Metadata = new Metadata(); + const generated: Metadata[] = await Promise.all( + this.creds.map((cred) => cred.generateMetadata(options)) + ); + for (const gen of generated) { + base.merge(gen); + } + return base; + } + + compose(other: CallCredentials): CallCredentials { + return new ComposedCallCredentials(this.creds.concat([other])); + } + + _equals(other: CallCredentials): boolean { + if (this === other) { + return true; + } + if (other instanceof ComposedCallCredentials) { + return this.creds.every((value, index) => + value._equals(other.creds[index]) + ); + } else { + return false; + } + } +} + +class SingleCallCredentials extends CallCredentials { + constructor(private metadataGenerator: CallMetadataGenerator) { + super(); + } + + generateMetadata(options: CallMetadataOptions): Promise { + return new Promise((resolve, reject) => { + this.metadataGenerator(options, (err, metadata) => { + if (metadata !== undefined) { + resolve(metadata); + } else { + reject(err); + } + }); + }); + } + + compose(other: CallCredentials): CallCredentials { + return new ComposedCallCredentials([this, other]); + } + + _equals(other: CallCredentials): boolean { + if (this === other) { + return true; + } + if (other instanceof SingleCallCredentials) { + return this.metadataGenerator === other.metadataGenerator; + } else { + return false; + } + } +} + +class EmptyCallCredentials extends CallCredentials { + generateMetadata(options: CallMetadataOptions): Promise { + return Promise.resolve(new Metadata()); + } + + compose(other: CallCredentials): CallCredentials { + return other; + } + + _equals(other: CallCredentials): boolean { + return other instanceof EmptyCallCredentials; + } +} diff --git a/packages/grpc-js/src/call-interface.ts b/packages/grpc-js/src/call-interface.ts new file mode 100644 index 000000000..283cb9b54 --- /dev/null +++ b/packages/grpc-js/src/call-interface.ts @@ -0,0 +1,173 @@ +/* + * Copyright 2022 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. + * + */ + +import { CallCredentials } from "./call-credentials"; +import { Status } from "./constants"; +import { Deadline } from "./deadline"; +import { Metadata } from "./metadata"; +import { ServerSurfaceCall } from "./server-call"; + +export interface CallStreamOptions { + deadline: Deadline; + flags: number; + host: string; + parentCall: ServerSurfaceCall | null; +} + +export type PartialCallStreamOptions = Partial; + +export interface StatusObject { + code: Status; + details: string; + metadata: Metadata; +} + +export type PartialStatusObject = Pick & { + metadata: Metadata | null; +} + +export const enum WriteFlags { + BufferHint = 1, + NoCompress = 2, + WriteThrough = 4, +} + +export interface WriteObject { + message: Buffer; + flags?: number; +} + +export interface MetadataListener { + (metadata: Metadata, next: (metadata: Metadata) => void): void; +} + +export interface MessageListener { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (message: any, next: (message: any) => void): void; +} + +export interface StatusListener { + (status: StatusObject, next: (status: StatusObject) => void): void; +} + +export interface FullListener { + onReceiveMetadata: MetadataListener; + onReceiveMessage: MessageListener; + onReceiveStatus: StatusListener; +} + +export type Listener = Partial; + +/** + * An object with methods for handling the responses to a call. + */ +export interface InterceptingListener { + onReceiveMetadata(metadata: Metadata): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage(message: any): void; + onReceiveStatus(status: StatusObject): void; +} + +export function isInterceptingListener( + listener: Listener | InterceptingListener +): listener is InterceptingListener { + return ( + listener.onReceiveMetadata !== undefined && + listener.onReceiveMetadata.length === 1 + ); +} + +export class InterceptingListenerImpl implements InterceptingListener { + private processingMetadata = false; + private hasPendingMessage = false; + private pendingMessage: any; + private processingMessage = false; + private pendingStatus: StatusObject | null = null; + constructor( + private listener: FullListener, + private nextListener: InterceptingListener + ) {} + + private processPendingMessage() { + if (this.hasPendingMessage) { + this.nextListener.onReceiveMessage(this.pendingMessage); + this.pendingMessage = null; + this.hasPendingMessage = false; + } + } + + private processPendingStatus() { + if (this.pendingStatus) { + this.nextListener.onReceiveStatus(this.pendingStatus); + } + } + + onReceiveMetadata(metadata: Metadata): void { + this.processingMetadata = true; + this.listener.onReceiveMetadata(metadata, (metadata) => { + this.processingMetadata = false; + this.nextListener.onReceiveMetadata(metadata); + this.processPendingMessage(); + this.processPendingStatus(); + }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage(message: any): void { + /* If this listener processes messages asynchronously, the last message may + * be reordered with respect to the status */ + this.processingMessage = true; + this.listener.onReceiveMessage(message, (msg) => { + this.processingMessage = false; + if (this.processingMetadata) { + this.pendingMessage = msg; + this.hasPendingMessage = true; + } else { + this.nextListener.onReceiveMessage(msg); + this.processPendingStatus(); + } + }); + } + onReceiveStatus(status: StatusObject): void { + this.listener.onReceiveStatus(status, (processedStatus) => { + if (this.processingMetadata || this.processingMessage) { + this.pendingStatus = processedStatus; + } else { + this.nextListener.onReceiveStatus(processedStatus); + } + }); + } +} + +export interface WriteCallback { + (error?: Error | null): void; +} + +export interface MessageContext { + callback?: WriteCallback; + flags?: number; +} + +export interface Call { + cancelWithStatus(status: Status, details: string): void; + getPeer(): string; + start(metadata: Metadata, listener: InterceptingListener): void; + sendMessageWithContext(context: MessageContext, message: Buffer): void; + startRead(): void; + halfClose(): void; + getCallNumber(): number; + setCredentials(credentials: CallCredentials): void; +} \ No newline at end of file diff --git a/packages/grpc-native-core/ext/timeval.h b/packages/grpc-js/src/call-number.ts similarity index 61% rename from packages/grpc-native-core/ext/timeval.h rename to packages/grpc-js/src/call-number.ts index 17d7f1642..48d34fac5 100644 --- a/packages/grpc-native-core/ext/timeval.h +++ b/packages/grpc-js/src/call-number.ts @@ -1,6 +1,5 @@ /* - * - * Copyright 2015 gRPC authors. + * Copyright 2022 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +15,8 @@ * */ -#ifndef NET_GRPC_NODE_TIMEVAL_H_ -#define NET_GRPC_NODE_TIMEVAL_H_ - -#include "grpc/support/time.h" - -namespace grpc { -namespace node { - -double TimespecToMilliseconds(gpr_timespec time); -gpr_timespec MillisecondsToTimespec(double millis); - -} // namespace node -} // namespace grpc +let nextCallNumber = 0; -#endif // NET_GRPC_NODE_TIMEVAL_H_ +export function getNextCallNumber() { + return nextCallNumber++; +} \ No newline at end of file diff --git a/packages/grpc-js/src/call.ts b/packages/grpc-js/src/call.ts new file mode 100644 index 000000000..bb6d74ccf --- /dev/null +++ b/packages/grpc-js/src/call.ts @@ -0,0 +1,195 @@ +/* + * Copyright 2019 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. + * + */ + +import { EventEmitter } from 'events'; +import { Duplex, Readable, Writable } from 'stream'; + +import { StatusObject, MessageContext } from './call-interface'; +import { Status } from './constants'; +import { EmitterAugmentation1 } from './events'; +import { Metadata } from './metadata'; +import { ObjectReadable, ObjectWritable, WriteCallback } from './object-stream'; +import { InterceptingCallInterface } from './client-interceptors'; + +/** + * A type extending the built-in Error object with additional fields. + */ +export type ServiceError = StatusObject & Error; + +/** + * A base type for all user-facing values returned by client-side method calls. + */ +export type SurfaceCall = { + call?: InterceptingCallInterface; + cancel(): void; + getPeer(): string; +} & EmitterAugmentation1<'metadata', Metadata> & + EmitterAugmentation1<'status', StatusObject> & + EventEmitter; + +/** + * A type representing the return value of a unary method call. + */ +export type ClientUnaryCall = SurfaceCall; + +/** + * A type representing the return value of a server stream method call. + */ +export type ClientReadableStream = { + deserialize: (chunk: Buffer) => ResponseType; +} & SurfaceCall & + ObjectReadable; + +/** + * A type representing the return value of a client stream method call. + */ +export type ClientWritableStream = { + serialize: (value: RequestType) => Buffer; +} & SurfaceCall & + ObjectWritable; + +/** + * A type representing the return value of a bidirectional stream method call. + */ +export type ClientDuplexStream< + RequestType, + ResponseType +> = ClientWritableStream & ClientReadableStream; + +/** + * Construct a ServiceError from a StatusObject. This function exists primarily + * as an attempt to make the error stack trace clearly communicate that the + * error is not necessarily a problem in gRPC itself. + * @param status + */ +export function callErrorFromStatus(status: StatusObject, callerStack: string): ServiceError { + const message = `${status.code} ${Status[status.code]}: ${status.details}`; + const error = new Error(message); + const stack = `${error.stack}\nfor call at\n${callerStack}`; + return Object.assign(new Error(message), status, {stack}); +} + +export class ClientUnaryCallImpl + extends EventEmitter + implements ClientUnaryCall { + public call?: InterceptingCallInterface; + constructor() { + super(); + } + + cancel(): void { + this.call?.cancelWithStatus(Status.CANCELLED, 'Cancelled on client'); + } + + getPeer(): string { + return this.call?.getPeer() ?? 'unknown'; + } +} + +export class ClientReadableStreamImpl + extends Readable + implements ClientReadableStream { + public call?: InterceptingCallInterface; + constructor(readonly deserialize: (chunk: Buffer) => ResponseType) { + super({ objectMode: true }); + } + + cancel(): void { + this.call?.cancelWithStatus(Status.CANCELLED, 'Cancelled on client'); + } + + getPeer(): string { + return this.call?.getPeer() ?? 'unknown'; + } + + _read(_size: number): void { + this.call?.startRead(); + } +} + +export class ClientWritableStreamImpl + extends Writable + implements ClientWritableStream { + public call?: InterceptingCallInterface; + constructor(readonly serialize: (value: RequestType) => Buffer) { + super({ objectMode: true }); + } + + cancel(): void { + this.call?.cancelWithStatus(Status.CANCELLED, 'Cancelled on client'); + } + + getPeer(): string { + return this.call?.getPeer() ?? 'unknown'; + } + + _write(chunk: RequestType, encoding: string, cb: WriteCallback) { + const context: MessageContext = { + callback: cb, + }; + const flags = Number(encoding); + if (!Number.isNaN(flags)) { + context.flags = flags; + } + this.call?.sendMessageWithContext(context, chunk); + } + + _final(cb: Function) { + this.call?.halfClose(); + cb(); + } +} + +export class ClientDuplexStreamImpl + extends Duplex + implements ClientDuplexStream { + public call?: InterceptingCallInterface; + constructor( + readonly serialize: (value: RequestType) => Buffer, + readonly deserialize: (chunk: Buffer) => ResponseType + ) { + super({ objectMode: true }); + } + + cancel(): void { + this.call?.cancelWithStatus(Status.CANCELLED, 'Cancelled on client'); + } + + getPeer(): string { + return this.call?.getPeer() ?? 'unknown'; + } + + _read(_size: number): void { + this.call?.startRead(); + } + + _write(chunk: RequestType, encoding: string, cb: WriteCallback) { + const context: MessageContext = { + callback: cb, + }; + const flags = Number(encoding); + if (!Number.isNaN(flags)) { + context.flags = flags; + } + this.call?.sendMessageWithContext(context, chunk); + } + + _final(cb: Function) { + this.call?.halfClose(); + cb(); + } +} diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts new file mode 100644 index 000000000..64db3e9eb --- /dev/null +++ b/packages/grpc-js/src/channel-credentials.ts @@ -0,0 +1,265 @@ +/* + * Copyright 2019 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. + * + */ + +import { ConnectionOptions, createSecureContext, PeerCertificate, SecureContext } from 'tls'; + +import { CallCredentials } from './call-credentials'; +import { CIPHER_SUITES, getDefaultRootsData } from './tls-helpers'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function verifyIsBufferOrNull(obj: any, friendlyName: string): void { + if (obj && !(obj instanceof Buffer)) { + throw new TypeError(`${friendlyName}, if provided, must be a Buffer.`); + } +} + +/** + * A callback that will receive the expected hostname and presented peer + * certificate as parameters. The callback should return an error to + * indicate that the presented certificate is considered invalid and + * otherwise returned undefined. + */ +export type CheckServerIdentityCallback = ( + hostname: string, + cert: PeerCertificate +) => Error | undefined; + +/** + * Additional peer verification options that can be set when creating + * SSL credentials. + */ +export interface VerifyOptions { + /** + * If set, this callback will be invoked after the usual hostname verification + * has been performed on the peer certificate. + */ + checkServerIdentity?: CheckServerIdentityCallback; +} + +/** + * A class that contains credentials for communicating over a channel, as well + * as a set of per-call credentials, which are applied to every method call made + * over a channel initialized with an instance of this class. + */ +export abstract class ChannelCredentials { + protected callCredentials: CallCredentials; + + protected constructor(callCredentials?: CallCredentials) { + this.callCredentials = callCredentials || CallCredentials.createEmpty(); + } + /** + * Returns a copy of this object with the included set of per-call credentials + * expanded to include callCredentials. + * @param callCredentials A CallCredentials object to associate with this + * instance. + */ + abstract compose(callCredentials: CallCredentials): ChannelCredentials; + + /** + * Gets the set of per-call credentials associated with this instance. + */ + _getCallCredentials(): CallCredentials { + return this.callCredentials; + } + + /** + * Gets a SecureContext object generated from input parameters if this + * instance was created with createSsl, or null if this instance was created + * with createInsecure. + */ + abstract _getConnectionOptions(): ConnectionOptions | null; + + /** + * Indicates whether this credentials object creates a secure channel. + */ + abstract _isSecure(): boolean; + + /** + * Check whether two channel credentials objects are equal. Two secure + * credentials are equal if they were constructed with the same parameters. + * @param other The other ChannelCredentials Object + */ + abstract _equals(other: ChannelCredentials): boolean; + + /** + * Return a new ChannelCredentials instance with a given set of credentials. + * The resulting instance can be used to construct a Channel that communicates + * over TLS. + * @param rootCerts The root certificate data. + * @param privateKey The client certificate private key, if available. + * @param certChain The client certificate key chain, if available. + * @param verifyOptions Additional options to modify certificate verification + */ + static createSsl( + rootCerts?: Buffer | null, + privateKey?: Buffer | null, + certChain?: Buffer | null, + verifyOptions?: VerifyOptions + ): ChannelCredentials { + verifyIsBufferOrNull(rootCerts, 'Root certificate'); + verifyIsBufferOrNull(privateKey, 'Private key'); + verifyIsBufferOrNull(certChain, 'Certificate chain'); + if (privateKey && !certChain) { + throw new Error( + 'Private key must be given with accompanying certificate chain' + ); + } + if (!privateKey && certChain) { + throw new Error( + 'Certificate chain must be given with accompanying private key' + ); + } + const secureContext = createSecureContext({ + ca: rootCerts ?? getDefaultRootsData() ?? undefined, + key: privateKey ?? undefined, + cert: certChain ?? undefined, + ciphers: CIPHER_SUITES, + }); + return new SecureChannelCredentialsImpl( + secureContext, + verifyOptions ?? {} + ); + } + + /** + * Return a new ChannelCredentials instance with credentials created using + * the provided secureContext. The resulting instances can be used to + * construct a Channel that communicates over TLS. gRPC will not override + * anything in the provided secureContext, so the environment variables + * GRPC_SSL_CIPHER_SUITES and GRPC_DEFAULT_SSL_ROOTS_FILE_PATH will + * not be applied. + * @param secureContext The return value of tls.createSecureContext() + * @param verifyOptions Additional options to modify certificate verification + */ + static createFromSecureContext(secureContext: SecureContext, verifyOptions?: VerifyOptions): ChannelCredentials { + return new SecureChannelCredentialsImpl( + secureContext, + verifyOptions ?? {} + ) + } + + /** + * Return a new ChannelCredentials instance with no credentials. + */ + static createInsecure(): ChannelCredentials { + return new InsecureChannelCredentialsImpl(); + } +} + +class InsecureChannelCredentialsImpl extends ChannelCredentials { + constructor(callCredentials?: CallCredentials) { + super(callCredentials); + } + + compose(callCredentials: CallCredentials): never { + throw new Error('Cannot compose insecure credentials'); + } + + _getConnectionOptions(): ConnectionOptions | null { + return null; + } + _isSecure(): boolean { + return false; + } + _equals(other: ChannelCredentials): boolean { + return other instanceof InsecureChannelCredentialsImpl; + } +} + +class SecureChannelCredentialsImpl extends ChannelCredentials { + connectionOptions: ConnectionOptions; + + constructor( + private secureContext: SecureContext, + private verifyOptions: VerifyOptions + ) { + super(); + this.connectionOptions = { + secureContext + }; + // Node asserts that this option is a function, so we cannot pass undefined + if (verifyOptions?.checkServerIdentity) { + this.connectionOptions.checkServerIdentity = verifyOptions.checkServerIdentity; + } + } + + compose(callCredentials: CallCredentials): ChannelCredentials { + const combinedCallCredentials = this.callCredentials.compose( + callCredentials + ); + return new ComposedChannelCredentialsImpl(this, combinedCallCredentials); + } + + _getConnectionOptions(): ConnectionOptions | null { + // Copy to prevent callers from mutating this.connectionOptions + return { ...this.connectionOptions }; + } + _isSecure(): boolean { + return true; + } + _equals(other: ChannelCredentials): boolean { + if (this === other) { + return true; + } + if (other instanceof SecureChannelCredentialsImpl) { + return ( + this.secureContext === other.secureContext && + this.verifyOptions.checkServerIdentity === other.verifyOptions.checkServerIdentity + ); + } else { + return false; + } + } +} + +class ComposedChannelCredentialsImpl extends ChannelCredentials { + constructor( + private channelCredentials: SecureChannelCredentialsImpl, + callCreds: CallCredentials + ) { + super(callCreds); + } + compose(callCredentials: CallCredentials) { + const combinedCallCredentials = this.callCredentials.compose( + callCredentials + ); + return new ComposedChannelCredentialsImpl( + this.channelCredentials, + combinedCallCredentials + ); + } + + _getConnectionOptions(): ConnectionOptions | null { + return this.channelCredentials._getConnectionOptions(); + } + _isSecure(): boolean { + return true; + } + _equals(other: ChannelCredentials): boolean { + if (this === other) { + return true; + } + if (other instanceof ComposedChannelCredentialsImpl) { + return ( + this.channelCredentials._equals(other.channelCredentials) && + this.callCredentials._equals(other.callCredentials) + ); + } else { + return false; + } + } +} diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts new file mode 100644 index 000000000..a41b89e9b --- /dev/null +++ b/packages/grpc-js/src/channel-options.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2019 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. + * + */ + +import { CompressionAlgorithms } from './compression-algorithms'; + +/** + * An interface that contains options used when initializing a Channel instance. + */ +export interface ChannelOptions { + 'grpc.ssl_target_name_override'?: string; + 'grpc.primary_user_agent'?: string; + 'grpc.secondary_user_agent'?: string; + 'grpc.default_authority'?: string; + 'grpc.keepalive_time_ms'?: number; + 'grpc.keepalive_timeout_ms'?: number; + 'grpc.keepalive_permit_without_calls'?: number; + 'grpc.service_config'?: string; + 'grpc.max_concurrent_streams'?: number; + 'grpc.initial_reconnect_backoff_ms'?: number; + 'grpc.max_reconnect_backoff_ms'?: number; + 'grpc.use_local_subchannel_pool'?: number; + 'grpc.max_send_message_length'?: number; + 'grpc.max_receive_message_length'?: number; + 'grpc.enable_http_proxy'?: number; + /* http_connect_target and http_connect_creds are used for passing data + * around internally, and should not be documented as public-facing options + */ + 'grpc.http_connect_target'?: string; + 'grpc.http_connect_creds'?: string; + 'grpc.default_compression_algorithm'?: CompressionAlgorithms; + 'grpc.enable_channelz'?: number; + 'grpc.dns_min_time_between_resolutions_ms'?: number; + 'grpc.enable_retries'?: number; + 'grpc.per_rpc_retry_buffer_size'?: number; + /* This option is pattered like a core option, but the core does not have + * this option. It is closely related to the option + * grpc.per_rpc_retry_buffer_size, which is in the core. The core will likely + * implement this functionality using the ResourceQuota mechanism, so there + * will probably not be any collision or other inconsistency. */ + 'grpc.retry_buffer_size'?: number; + 'grpc.max_connection_age_ms'?: number; + 'grpc.max_connection_age_grace_ms'?: number; + 'grpc-node.max_session_memory'?: number; + 'grpc.service_config_disable_resolution'?: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} + +/** + * This is for checking provided options at runtime. This is an object for + * easier membership checking. + */ +export const recognizedOptions = { + 'grpc.ssl_target_name_override': true, + 'grpc.primary_user_agent': true, + 'grpc.secondary_user_agent': true, + 'grpc.default_authority': true, + 'grpc.keepalive_time_ms': true, + 'grpc.keepalive_timeout_ms': true, + 'grpc.keepalive_permit_without_calls': true, + 'grpc.service_config': true, + 'grpc.max_concurrent_streams': true, + 'grpc.initial_reconnect_backoff_ms': true, + 'grpc.max_reconnect_backoff_ms': true, + 'grpc.use_local_subchannel_pool': true, + 'grpc.max_send_message_length': true, + 'grpc.max_receive_message_length': true, + 'grpc.enable_http_proxy': true, + 'grpc.enable_channelz': true, + 'grpc.dns_min_time_between_resolutions_ms': true, + 'grpc.enable_retries': true, + 'grpc.per_rpc_retry_buffer_size': true, + 'grpc.retry_buffer_size': true, + 'grpc.max_connection_age_ms': true, + 'grpc.max_connection_age_grace_ms': true, + 'grpc-node.max_session_memory': true, + 'grpc.service_config_disable_resolution': true, +}; + +export function channelOptionsEqual( + options1: ChannelOptions, + options2: ChannelOptions +) { + const keys1 = Object.keys(options1).sort(); + const keys2 = Object.keys(options2).sort(); + if (keys1.length !== keys2.length) { + return false; + } + for (let i = 0; i < keys1.length; i += 1) { + if (keys1[i] !== keys2[i]) { + return false; + } + if (options1[keys1[i]] !== options2[keys2[i]]) { + return false; + } + } + return true; +} diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts new file mode 100644 index 000000000..aaf14bb22 --- /dev/null +++ b/packages/grpc-js/src/channel.ts @@ -0,0 +1,165 @@ +/* + * Copyright 2019 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. + * + */ + +import { ChannelCredentials } from './channel-credentials'; +import { ChannelOptions } from './channel-options'; +import { ServerSurfaceCall } from './server-call'; + +import { ConnectivityState } from './connectivity-state'; +import { ChannelRef } from './channelz'; +import { Call } from './call-interface'; +import { InternalChannel } from './internal-channel'; +import { Deadline } from './deadline'; + +/** + * An interface that represents a communication channel to a server specified + * by a given address. + */ +export interface Channel { + /** + * Close the channel. This has the same functionality as the existing + * grpc.Client.prototype.close + */ + close(): void; + /** + * Return the target that this channel connects to + */ + getTarget(): string; + /** + * Get the channel's current connectivity state. This method is here mainly + * because it is in the existing internal Channel class, and there isn't + * another good place to put it. + * @param tryToConnect If true, the channel will start connecting if it is + * idle. Otherwise, idle channels will only start connecting when a + * call starts. + */ + getConnectivityState(tryToConnect: boolean): ConnectivityState; + /** + * Watch for connectivity state changes. This is also here mainly because + * it is in the existing external Channel class. + * @param currentState The state to watch for transitions from. This should + * always be populated by calling getConnectivityState immediately + * before. + * @param deadline A deadline for waiting for a state change + * @param callback Called with no error when a state change, or with an + * error if the deadline passes without a state change. + */ + watchConnectivityState( + currentState: ConnectivityState, + deadline: Date | number, + callback: (error?: Error) => void + ): void; + /** + * Get the channelz reference object for this channel. A request to the + * channelz service for the id in this object will provide information + * about this channel. + */ + getChannelzRef(): ChannelRef; + /** + * Create a call object. Call is an opaque type that is used by the Client + * class. This function is called by the gRPC library when starting a + * request. Implementers should return an instance of Call that is returned + * from calling createCall on an instance of the provided Channel class. + * @param method The full method string to request. + * @param deadline The call deadline + * @param host A host string override for making the request + * @param parentCall A server call to propagate some information from + * @param propagateFlags A bitwise combination of elements of grpc.propagate + * that indicates what information to propagate from parentCall. + */ + createCall( + method: string, + deadline: Deadline, + host: string | null | undefined, + parentCall: ServerSurfaceCall | null, + propagateFlags: number | null | undefined + ): Call; +} + +export class ChannelImplementation implements Channel { + + private internalChannel: InternalChannel; + + constructor( + target: string, + credentials: ChannelCredentials, + options: ChannelOptions + ) { + if (typeof target !== 'string') { + throw new TypeError('Channel target must be a string'); + } + if (!(credentials instanceof ChannelCredentials)) { + throw new TypeError( + 'Channel credentials must be a ChannelCredentials object' + ); + } + if (options) { + if (typeof options !== 'object') { + throw new TypeError('Channel options must be an object'); + } + } + + this.internalChannel = new InternalChannel(target, credentials, options); + } + + close() { + this.internalChannel.close(); + } + + getTarget() { + return this.internalChannel.getTarget(); + } + + getConnectivityState(tryToConnect: boolean) { + return this.internalChannel.getConnectivityState(tryToConnect); + } + + watchConnectivityState( + currentState: ConnectivityState, + deadline: Date | number, + callback: (error?: Error) => void + ): void { + this.internalChannel.watchConnectivityState(currentState, deadline, callback); + } + + /** + * Get the channelz reference object for this channel. The returned value is + * garbage if channelz is disabled for this channel. + * @returns + */ + getChannelzRef() { + return this.internalChannel.getChannelzRef(); + } + + createCall( + method: string, + deadline: Deadline, + host: string | null | undefined, + parentCall: ServerSurfaceCall | null, + propagateFlags: number | null | undefined + ): Call { + if (typeof method !== 'string') { + throw new TypeError('Channel#createCall: method must be a string'); + } + if (!(typeof deadline === 'number' || deadline instanceof Date)) { + throw new TypeError( + 'Channel#createCall: deadline must be a number or Date' + ); + } + return this.internalChannel.createCall(method, deadline, host, parentCall, propagateFlags); + } +} diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts new file mode 100644 index 000000000..5a7a54760 --- /dev/null +++ b/packages/grpc-js/src/channelz.ts @@ -0,0 +1,758 @@ +/* + * Copyright 2021 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. + * + */ + +import { isIPv4, isIPv6 } from "net"; +import { ConnectivityState } from "./connectivity-state"; +import { Status } from "./constants"; +import { Timestamp } from "./generated/google/protobuf/Timestamp"; +import { Channel as ChannelMessage } from "./generated/grpc/channelz/v1/Channel"; +import { ChannelConnectivityState__Output } from "./generated/grpc/channelz/v1/ChannelConnectivityState"; +import { ChannelRef as ChannelRefMessage } from "./generated/grpc/channelz/v1/ChannelRef"; +import { ChannelTrace } from "./generated/grpc/channelz/v1/ChannelTrace"; +import { GetChannelRequest__Output } from "./generated/grpc/channelz/v1/GetChannelRequest"; +import { GetChannelResponse } from "./generated/grpc/channelz/v1/GetChannelResponse"; +import { sendUnaryData, ServerUnaryCall } from "./server-call"; +import { ServerRef as ServerRefMessage } from "./generated/grpc/channelz/v1/ServerRef"; +import { SocketRef as SocketRefMessage } from "./generated/grpc/channelz/v1/SocketRef"; +import { isTcpSubchannelAddress, SubchannelAddress } from "./subchannel-address"; +import { SubchannelRef as SubchannelRefMessage } from "./generated/grpc/channelz/v1/SubchannelRef"; +import { GetServerRequest__Output } from "./generated/grpc/channelz/v1/GetServerRequest"; +import { GetServerResponse } from "./generated/grpc/channelz/v1/GetServerResponse"; +import { Server as ServerMessage } from "./generated/grpc/channelz/v1/Server"; +import { GetServersRequest__Output } from "./generated/grpc/channelz/v1/GetServersRequest"; +import { GetServersResponse } from "./generated/grpc/channelz/v1/GetServersResponse"; +import { GetTopChannelsRequest__Output } from "./generated/grpc/channelz/v1/GetTopChannelsRequest"; +import { GetTopChannelsResponse } from "./generated/grpc/channelz/v1/GetTopChannelsResponse"; +import { GetSubchannelRequest__Output } from "./generated/grpc/channelz/v1/GetSubchannelRequest"; +import { GetSubchannelResponse } from "./generated/grpc/channelz/v1/GetSubchannelResponse"; +import { Subchannel as SubchannelMessage } from "./generated/grpc/channelz/v1/Subchannel"; +import { GetSocketRequest__Output } from "./generated/grpc/channelz/v1/GetSocketRequest"; +import { GetSocketResponse } from "./generated/grpc/channelz/v1/GetSocketResponse"; +import { Socket as SocketMessage } from "./generated/grpc/channelz/v1/Socket"; +import { Address } from "./generated/grpc/channelz/v1/Address"; +import { Security } from "./generated/grpc/channelz/v1/Security"; +import { GetServerSocketsRequest__Output } from "./generated/grpc/channelz/v1/GetServerSocketsRequest"; +import { GetServerSocketsResponse } from "./generated/grpc/channelz/v1/GetServerSocketsResponse"; +import { ChannelzDefinition, ChannelzHandlers } from "./generated/grpc/channelz/v1/Channelz"; +import { ProtoGrpcType as ChannelzProtoGrpcType } from "./generated/channelz"; +import type { loadSync } from '@grpc/proto-loader'; +import { registerAdminService } from "./admin"; +import { loadPackageDefinition } from "./make-client"; + +export type TraceSeverity = 'CT_UNKNOWN' | 'CT_INFO' | 'CT_WARNING' | 'CT_ERROR'; + +export interface ChannelRef { + kind: 'channel'; + id: number; + name: string; +} + +export interface SubchannelRef { + kind: 'subchannel'; + id: number; + name: string; +} + +export interface ServerRef { + kind: 'server'; + id: number; +} + +export interface SocketRef { + kind: 'socket'; + id: number; + name: string; +} + +function channelRefToMessage(ref: ChannelRef): ChannelRefMessage { + return { + channel_id: ref.id, + name: ref.name + }; +} + +function subchannelRefToMessage(ref: SubchannelRef): SubchannelRefMessage { + return { + subchannel_id: ref.id, + name: ref.name + } +} + +function serverRefToMessage(ref: ServerRef): ServerRefMessage { + return { + server_id: ref.id + } +} + +function socketRefToMessage(ref: SocketRef): SocketRefMessage { + return { + socket_id: ref.id, + name: ref.name + } +} + +interface TraceEvent { + description: string; + severity: TraceSeverity; + timestamp: Date; + childChannel?: ChannelRef; + childSubchannel?: SubchannelRef; +} + +/** + * The loose upper bound on the number of events that should be retained in a + * trace. This may be exceeded by up to a factor of 2. Arbitrarily chosen as a + * number that should be large enough to contain the recent relevant + * information, but small enough to not use excessive memory. + */ +const TARGET_RETAINED_TRACES = 32; + +export class ChannelzTrace { + events: TraceEvent[] = []; + creationTimestamp: Date; + eventsLogged: number = 0; + + constructor() { + this.creationTimestamp = new Date(); + } + + addTrace(severity: TraceSeverity, description: string, child?: ChannelRef | SubchannelRef) { + const timestamp = new Date(); + this.events.push({ + description: description, + severity: severity, + timestamp: timestamp, + childChannel: child?.kind === 'channel' ? child : undefined, + childSubchannel: child?.kind === 'subchannel' ? child : undefined + }); + // Whenever the trace array gets too large, discard the first half + if (this.events.length >= TARGET_RETAINED_TRACES * 2) { + this.events = this.events.slice(TARGET_RETAINED_TRACES); + } + this.eventsLogged += 1; + } + + getTraceMessage(): ChannelTrace { + return { + creation_timestamp: dateToProtoTimestamp(this.creationTimestamp), + num_events_logged: this.eventsLogged, + events: this.events.map(event => { + return { + description: event.description, + severity: event.severity, + timestamp: dateToProtoTimestamp(event.timestamp), + channel_ref: event.childChannel ? channelRefToMessage(event.childChannel) : null, + subchannel_ref: event.childSubchannel ? subchannelRefToMessage(event.childSubchannel) : null + } + }) + }; + } +} + +export class ChannelzChildrenTracker { + private channelChildren: Map = new Map(); + private subchannelChildren: Map = new Map(); + private socketChildren: Map = new Map(); + + refChild(child: ChannelRef | SubchannelRef | SocketRef) { + switch (child.kind) { + case 'channel': { + let trackedChild = this.channelChildren.get(child.id) ?? {ref: child, count: 0}; + trackedChild.count += 1; + this.channelChildren.set(child.id, trackedChild); + break; + } + case 'subchannel':{ + let trackedChild = this.subchannelChildren.get(child.id) ?? {ref: child, count: 0}; + trackedChild.count += 1; + this.subchannelChildren.set(child.id, trackedChild); + break; + } + case 'socket':{ + let trackedChild = this.socketChildren.get(child.id) ?? {ref: child, count: 0}; + trackedChild.count += 1; + this.socketChildren.set(child.id, trackedChild); + break; + } + } + } + + unrefChild(child: ChannelRef | SubchannelRef | SocketRef) { + switch (child.kind) { + case 'channel': { + let trackedChild = this.channelChildren.get(child.id); + if (trackedChild !== undefined) { + trackedChild.count -= 1; + if (trackedChild.count === 0) { + this.channelChildren.delete(child.id); + } else { + this.channelChildren.set(child.id, trackedChild); + } + } + break; + } + case 'subchannel': { + let trackedChild = this.subchannelChildren.get(child.id); + if (trackedChild !== undefined) { + trackedChild.count -= 1; + if (trackedChild.count === 0) { + this.subchannelChildren.delete(child.id); + } else { + this.subchannelChildren.set(child.id, trackedChild); + } + } + break; + } + case 'socket': { + let trackedChild = this.socketChildren.get(child.id); + if (trackedChild !== undefined) { + trackedChild.count -= 1; + if (trackedChild.count === 0) { + this.socketChildren.delete(child.id); + } else { + this.socketChildren.set(child.id, trackedChild); + } + } + break; + } + } + } + + getChildLists(): ChannelzChildren { + const channels: ChannelRef[] = []; + for (const {ref} of this.channelChildren.values()) { + channels.push(ref); + } + const subchannels: SubchannelRef[] = []; + for (const {ref} of this.subchannelChildren.values()) { + subchannels.push(ref); + } + const sockets: SocketRef[] = []; + for (const {ref} of this.socketChildren.values()) { + sockets.push(ref); + } + return {channels, subchannels, sockets}; + } +} + +export class ChannelzCallTracker { + callsStarted: number = 0; + callsSucceeded: number = 0; + callsFailed: number = 0; + lastCallStartedTimestamp: Date | null = null; + + addCallStarted() { + this.callsStarted += 1; + this.lastCallStartedTimestamp = new Date(); + } + addCallSucceeded() { + this.callsSucceeded += 1; + } + addCallFailed() { + this.callsFailed += 1; + } +} + +export interface ChannelzChildren { + channels: ChannelRef[]; + subchannels: SubchannelRef[]; + sockets: SocketRef[]; +} + +export interface ChannelInfo { + target: string; + state: ConnectivityState; + trace: ChannelzTrace; + callTracker: ChannelzCallTracker; + children: ChannelzChildren; +} + +export interface SubchannelInfo extends ChannelInfo {} + +export interface ServerInfo { + trace: ChannelzTrace; + callTracker: ChannelzCallTracker; + listenerChildren: ChannelzChildren; + sessionChildren: ChannelzChildren; +} + +export interface TlsInfo { + cipherSuiteStandardName: string | null; + cipherSuiteOtherName: string | null; + localCertificate: Buffer | null; + remoteCertificate: Buffer | null; +} + +export interface SocketInfo { + localAddress: SubchannelAddress | null; + remoteAddress: SubchannelAddress | null; + security: TlsInfo | null; + remoteName: string | null; + streamsStarted: number; + streamsSucceeded: number; + streamsFailed: number; + messagesSent: number; + messagesReceived: number; + keepAlivesSent: number; + lastLocalStreamCreatedTimestamp: Date | null; + lastRemoteStreamCreatedTimestamp: Date | null; + lastMessageSentTimestamp: Date | null; + lastMessageReceivedTimestamp: Date | null; + localFlowControlWindow: number | null; + remoteFlowControlWindow: number | null; +} + +interface ChannelEntry { + ref: ChannelRef; + getInfo(): ChannelInfo; +} + +interface SubchannelEntry { + ref: SubchannelRef; + getInfo(): SubchannelInfo; +} + +interface ServerEntry { + ref: ServerRef; + getInfo(): ServerInfo; +} + +interface SocketEntry { + ref: SocketRef; + getInfo(): SocketInfo; +} + +let nextId = 1; + +function getNextId(): number { + return nextId++; +} + +const channels: (ChannelEntry | undefined)[] = []; +const subchannels: (SubchannelEntry | undefined)[] = []; +const servers: (ServerEntry | undefined)[] = []; +const sockets: (SocketEntry | undefined)[] = []; + +export function registerChannelzChannel(name: string, getInfo: () => ChannelInfo, channelzEnabled: boolean): ChannelRef { + const id = getNextId(); + const ref: ChannelRef = {id, name, kind: 'channel'}; + if (channelzEnabled) { + channels[id] = { ref, getInfo }; + } + return ref; +} + +export function registerChannelzSubchannel(name: string, getInfo:() => SubchannelInfo, channelzEnabled: boolean): SubchannelRef { + const id = getNextId(); + const ref: SubchannelRef = {id, name, kind: 'subchannel'}; + if (channelzEnabled) { + subchannels[id] = { ref, getInfo }; + } + return ref; +} + +export function registerChannelzServer(getInfo: () => ServerInfo, channelzEnabled: boolean): ServerRef { + const id = getNextId(); + const ref: ServerRef = {id, kind: 'server'}; + if (channelzEnabled) { + servers[id] = { ref, getInfo }; + } + return ref; +} + +export function registerChannelzSocket(name: string, getInfo: () => SocketInfo, channelzEnabled: boolean): SocketRef { + const id = getNextId(); + const ref: SocketRef = {id, name, kind: 'socket'}; + if (channelzEnabled) { + sockets[id] = { ref, getInfo}; + } + return ref; +} + +export function unregisterChannelzRef(ref: ChannelRef | SubchannelRef | ServerRef | SocketRef) { + switch (ref.kind) { + case 'channel': + delete channels[ref.id]; + return; + case 'subchannel': + delete subchannels[ref.id]; + return; + case 'server': + delete servers[ref.id]; + return; + case 'socket': + delete sockets[ref.id]; + return; + } +} + +/** + * Parse a single section of an IPv6 address as two bytes + * @param addressSection A hexadecimal string of length up to 4 + * @returns The pair of bytes representing this address section + */ +function parseIPv6Section(addressSection: string): [number, number] { + const numberValue = Number.parseInt(addressSection, 16); + return [numberValue / 256 | 0, numberValue % 256]; +} + +/** + * Parse a chunk of an IPv6 address string to some number of bytes + * @param addressChunk Some number of segments of up to 4 hexadecimal + * characters each, joined by colons. + * @returns The list of bytes representing this address chunk + */ +function parseIPv6Chunk(addressChunk: string): number[] { + if (addressChunk === '') { + return []; + } + const bytePairs = addressChunk.split(':').map(section => parseIPv6Section(section)); + const result: number[] = []; + return result.concat(...bytePairs); +} + +/** + * Converts an IPv4 or IPv6 address from string representation to binary + * representation + * @param ipAddress an IP address in standard IPv4 or IPv6 text format + * @returns + */ +function ipAddressStringToBuffer(ipAddress: string): Buffer | null { + if (isIPv4(ipAddress)) { + return Buffer.from(Uint8Array.from(ipAddress.split('.').map(segment => Number.parseInt(segment)))); + } else if (isIPv6(ipAddress)) { + let leftSection: string; + let rightSection: string; + const doubleColonIndex = ipAddress.indexOf('::'); + if (doubleColonIndex === -1) { + leftSection = ipAddress; + rightSection = ''; + } else { + leftSection = ipAddress.substring(0, doubleColonIndex); + rightSection = ipAddress.substring(doubleColonIndex + 2); + } + const leftBuffer = Buffer.from(parseIPv6Chunk(leftSection)); + const rightBuffer = Buffer.from(parseIPv6Chunk(rightSection)); + const middleBuffer = Buffer.alloc(16 - leftBuffer.length - rightBuffer.length, 0); + return Buffer.concat([leftBuffer, middleBuffer, rightBuffer]); + } else { + return null; + } +} + +function connectivityStateToMessage(state: ConnectivityState): ChannelConnectivityState__Output { + switch (state) { + case ConnectivityState.CONNECTING: + return { + state: 'CONNECTING' + }; + case ConnectivityState.IDLE: + return { + state: 'IDLE' + }; + case ConnectivityState.READY: + return { + state: 'READY' + }; + case ConnectivityState.SHUTDOWN: + return { + state: 'SHUTDOWN' + }; + case ConnectivityState.TRANSIENT_FAILURE: + return { + state: 'TRANSIENT_FAILURE' + }; + default: + return { + state: 'UNKNOWN' + }; + } +} + +function dateToProtoTimestamp(date?: Date | null): Timestamp | null { + if (!date) { + return null; + } + const millisSinceEpoch = date.getTime(); + return { + seconds: (millisSinceEpoch / 1000) | 0, + nanos: (millisSinceEpoch % 1000) * 1_000_000 + } +} + +function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage { + const resolvedInfo = channelEntry.getInfo(); + return { + ref: channelRefToMessage(channelEntry.ref), + data: { + target: resolvedInfo.target, + state: connectivityStateToMessage(resolvedInfo.state), + calls_started: resolvedInfo.callTracker.callsStarted, + calls_succeeded: resolvedInfo.callTracker.callsSucceeded, + calls_failed: resolvedInfo.callTracker.callsFailed, + last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp), + trace: resolvedInfo.trace.getTraceMessage() + }, + channel_ref: resolvedInfo.children.channels.map(ref => channelRefToMessage(ref)), + subchannel_ref: resolvedInfo.children.subchannels.map(ref => subchannelRefToMessage(ref)) + }; +} + +function GetChannel(call: ServerUnaryCall, callback: sendUnaryData): void { + const channelId = Number.parseInt(call.request.channel_id); + const channelEntry = channels[channelId]; + if (channelEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No channel data found for id ' + channelId + }); + return; + } + callback(null, {channel: getChannelMessage(channelEntry)}); +} + +function GetTopChannels(call: ServerUnaryCall, callback: sendUnaryData): void { + const maxResults = Number.parseInt(call.request.max_results); + const resultList: ChannelMessage[] = []; + let i = Number.parseInt(call.request.start_channel_id); + for (; i < channels.length; i++) { + const channelEntry = channels[i]; + if (channelEntry === undefined) { + continue; + } + resultList.push(getChannelMessage(channelEntry)); + if (resultList.length >= maxResults) { + break; + } + } + callback(null, { + channel: resultList, + end: i >= servers.length + }); +} + +function getServerMessage(serverEntry: ServerEntry): ServerMessage { + const resolvedInfo = serverEntry.getInfo(); + return { + ref: serverRefToMessage(serverEntry.ref), + data: { + calls_started: resolvedInfo.callTracker.callsStarted, + calls_succeeded: resolvedInfo.callTracker.callsSucceeded, + calls_failed: resolvedInfo.callTracker.callsFailed, + last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp), + trace: resolvedInfo.trace.getTraceMessage() + }, + listen_socket: resolvedInfo.listenerChildren.sockets.map(ref => socketRefToMessage(ref)) + }; +} + +function GetServer(call: ServerUnaryCall, callback: sendUnaryData): void { + const serverId = Number.parseInt(call.request.server_id); + const serverEntry = servers[serverId]; + if (serverEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No server data found for id ' + serverId + }); + return; + } + callback(null, {server: getServerMessage(serverEntry)}); +} + +function GetServers(call: ServerUnaryCall, callback: sendUnaryData): void { + const maxResults = Number.parseInt(call.request.max_results); + const resultList: ServerMessage[] = []; + let i = Number.parseInt(call.request.start_server_id); + for (; i < servers.length; i++) { + const serverEntry = servers[i]; + if (serverEntry === undefined) { + continue; + } + resultList.push(getServerMessage(serverEntry)); + if (resultList.length >= maxResults) { + break; + } + } + callback(null, { + server: resultList, + end: i >= servers.length + }); +} + +function GetSubchannel(call: ServerUnaryCall, callback: sendUnaryData): void { + const subchannelId = Number.parseInt(call.request.subchannel_id); + const subchannelEntry = subchannels[subchannelId]; + if (subchannelEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No subchannel data found for id ' + subchannelId + }); + return; + } + const resolvedInfo = subchannelEntry.getInfo(); + const subchannelMessage: SubchannelMessage = { + ref: subchannelRefToMessage(subchannelEntry.ref), + data: { + target: resolvedInfo.target, + state: connectivityStateToMessage(resolvedInfo.state), + calls_started: resolvedInfo.callTracker.callsStarted, + calls_succeeded: resolvedInfo.callTracker.callsSucceeded, + calls_failed: resolvedInfo.callTracker.callsFailed, + last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp), + trace: resolvedInfo.trace.getTraceMessage() + }, + socket_ref: resolvedInfo.children.sockets.map(ref => socketRefToMessage(ref)) + }; + callback(null, {subchannel: subchannelMessage}); +} + +function subchannelAddressToAddressMessage(subchannelAddress: SubchannelAddress): Address { + if (isTcpSubchannelAddress(subchannelAddress)) { + return { + address: 'tcpip_address', + tcpip_address: { + ip_address: ipAddressStringToBuffer(subchannelAddress.host) ?? undefined, + port: subchannelAddress.port + } + }; + } else { + return { + address: 'uds_address', + uds_address: { + filename: subchannelAddress.path + } + }; + } +} + +function GetSocket(call: ServerUnaryCall, callback: sendUnaryData): void { + const socketId = Number.parseInt(call.request.socket_id); + const socketEntry = sockets[socketId]; + if (socketEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No socket data found for id ' + socketId + }); + return; + } + const resolvedInfo = socketEntry.getInfo(); + const securityMessage: Security | null = resolvedInfo.security ? { + model: 'tls', + tls: { + cipher_suite: resolvedInfo.security.cipherSuiteStandardName ? 'standard_name' : 'other_name', + standard_name: resolvedInfo.security.cipherSuiteStandardName ?? undefined, + other_name: resolvedInfo.security.cipherSuiteOtherName ?? undefined, + local_certificate: resolvedInfo.security.localCertificate ?? undefined, + remote_certificate: resolvedInfo.security.remoteCertificate ?? undefined + } + } : null; + const socketMessage: SocketMessage = { + ref: socketRefToMessage(socketEntry.ref), + local: resolvedInfo.localAddress ? subchannelAddressToAddressMessage(resolvedInfo.localAddress) : null, + remote: resolvedInfo.remoteAddress ? subchannelAddressToAddressMessage(resolvedInfo.remoteAddress) : null, + remote_name: resolvedInfo.remoteName ?? undefined, + security: securityMessage, + data: { + keep_alives_sent: resolvedInfo.keepAlivesSent, + streams_started: resolvedInfo.streamsStarted, + streams_succeeded: resolvedInfo.streamsSucceeded, + streams_failed: resolvedInfo.streamsFailed, + last_local_stream_created_timestamp: dateToProtoTimestamp(resolvedInfo.lastLocalStreamCreatedTimestamp), + last_remote_stream_created_timestamp: dateToProtoTimestamp(resolvedInfo.lastRemoteStreamCreatedTimestamp), + messages_received: resolvedInfo.messagesReceived, + messages_sent: resolvedInfo.messagesSent, + last_message_received_timestamp: dateToProtoTimestamp(resolvedInfo.lastMessageReceivedTimestamp), + last_message_sent_timestamp: dateToProtoTimestamp(resolvedInfo.lastMessageSentTimestamp), + local_flow_control_window: resolvedInfo.localFlowControlWindow ? { value: resolvedInfo.localFlowControlWindow } : null, + remote_flow_control_window: resolvedInfo.remoteFlowControlWindow ? { value: resolvedInfo.remoteFlowControlWindow } : null, + } + }; + callback(null, {socket: socketMessage}); +} + +function GetServerSockets(call: ServerUnaryCall, callback: sendUnaryData): void { + const serverId = Number.parseInt(call.request.server_id); + const serverEntry = servers[serverId]; + if (serverEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No server data found for id ' + serverId + }); + return; + } + const startId = Number.parseInt(call.request.start_socket_id); + const maxResults = Number.parseInt(call.request.max_results); + const resolvedInfo = serverEntry.getInfo(); + // If we wanted to include listener sockets in the result, this line would + // instead say + // const allSockets = resolvedInfo.listenerChildren.sockets.concat(resolvedInfo.sessionChildren.sockets).sort((ref1, ref2) => ref1.id - ref2.id); + const allSockets = resolvedInfo.sessionChildren.sockets.sort((ref1, ref2) => ref1.id - ref2.id); + const resultList: SocketRefMessage[] = []; + let i = 0; + for (; i < allSockets.length; i++) { + if (allSockets[i].id >= startId) { + resultList.push(socketRefToMessage(allSockets[i])); + if (resultList.length >= maxResults) { + break; + } + } + } + callback(null, { + socket_ref: resultList, + end: i >= allSockets.length + }); +} + +export function getChannelzHandlers(): ChannelzHandlers { + return { + GetChannel, + GetTopChannels, + GetServer, + GetServers, + GetSubchannel, + GetSocket, + GetServerSockets + }; +} + +let loadedChannelzDefinition: ChannelzDefinition | null = null; + +export function getChannelzServiceDefinition(): ChannelzDefinition { + if (loadedChannelzDefinition) { + return loadedChannelzDefinition; + } + /* The purpose of this complexity is to avoid loading @grpc/proto-loader at + * runtime for users who will not use/enable channelz. */ + const loaderLoadSync = require('@grpc/proto-loader').loadSync as typeof loadSync; + const loadedProto = loaderLoadSync('channelz.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + `${__dirname}/../../proto` + ] + }); + const channelzGrpcObject = loadPackageDefinition(loadedProto) as unknown as ChannelzProtoGrpcType; + loadedChannelzDefinition = channelzGrpcObject.grpc.channelz.v1.Channelz.service; + return loadedChannelzDefinition; +} + +export function setup() { + registerAdminService(getChannelzServiceDefinition, getChannelzHandlers); +} \ No newline at end of file diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts new file mode 100644 index 000000000..277a14f59 --- /dev/null +++ b/packages/grpc-js/src/client-interceptors.ts @@ -0,0 +1,573 @@ +/* + * Copyright 2019 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. + * + */ + +import { Metadata } from './metadata'; +import { + StatusObject, + Listener, + MetadataListener, + MessageListener, + StatusListener, + FullListener, + InterceptingListener, + InterceptingListenerImpl, + isInterceptingListener, + MessageContext, + Call, +} from './call-interface'; +import { Status } from './constants'; +import { Channel } from './channel'; +import { CallOptions } from './client'; +import { ClientMethodDefinition } from './make-client'; +import { getErrorMessage } from './error'; + +/** + * Error class associated with passing both interceptors and interceptor + * providers to a client constructor or as call options. + */ +export class InterceptorConfigurationError extends Error { + constructor(message: string) { + super(message); + this.name = 'InterceptorConfigurationError'; + Error.captureStackTrace(this, InterceptorConfigurationError); + } +} + +export interface MetadataRequester { + ( + metadata: Metadata, + listener: InterceptingListener, + next: ( + metadata: Metadata, + listener: InterceptingListener | Listener + ) => void + ): void; +} + +export interface MessageRequester { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (message: any, next: (message: any) => void): void; +} + +export interface CloseRequester { + (next: () => void): void; +} + +export interface CancelRequester { + (next: () => void): void; +} + +/** + * An object with methods for intercepting and modifying outgoing call operations. + */ +export interface FullRequester { + start: MetadataRequester; + sendMessage: MessageRequester; + halfClose: CloseRequester; + cancel: CancelRequester; +} + +export type Requester = Partial; + +export class ListenerBuilder { + private metadata: MetadataListener | undefined = undefined; + private message: MessageListener | undefined = undefined; + private status: StatusListener | undefined = undefined; + + withOnReceiveMetadata(onReceiveMetadata: MetadataListener): this { + this.metadata = onReceiveMetadata; + return this; + } + + withOnReceiveMessage(onReceiveMessage: MessageListener): this { + this.message = onReceiveMessage; + return this; + } + + withOnReceiveStatus(onReceiveStatus: StatusListener): this { + this.status = onReceiveStatus; + return this; + } + + build(): Listener { + return { + onReceiveMetadata: this.metadata, + onReceiveMessage: this.message, + onReceiveStatus: this.status, + }; + } +} + +export class RequesterBuilder { + private start: MetadataRequester | undefined = undefined; + private message: MessageRequester | undefined = undefined; + private halfClose: CloseRequester | undefined = undefined; + private cancel: CancelRequester | undefined = undefined; + + withStart(start: MetadataRequester): this { + this.start = start; + return this; + } + + withSendMessage(sendMessage: MessageRequester): this { + this.message = sendMessage; + return this; + } + + withHalfClose(halfClose: CloseRequester): this { + this.halfClose = halfClose; + return this; + } + + withCancel(cancel: CancelRequester): this { + this.cancel = cancel; + return this; + } + + build(): Requester { + return { + start: this.start, + sendMessage: this.message, + halfClose: this.halfClose, + cancel: this.cancel, + }; + } +} + +/** + * A Listener with a default pass-through implementation of each method. Used + * for filling out Listeners with some methods omitted. + */ +const defaultListener: FullListener = { + onReceiveMetadata: (metadata, next) => { + next(metadata); + }, + onReceiveMessage: (message, next) => { + next(message); + }, + onReceiveStatus: (status, next) => { + next(status); + }, +}; + +/** + * A Requester with a default pass-through implementation of each method. Used + * for filling out Requesters with some methods omitted. + */ +const defaultRequester: FullRequester = { + start: (metadata, listener, next) => { + next(metadata, listener); + }, + sendMessage: (message, next) => { + next(message); + }, + halfClose: (next) => { + next(); + }, + cancel: (next) => { + next(); + }, +}; + +export interface InterceptorOptions extends CallOptions { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + method_definition: ClientMethodDefinition; +} + +export interface InterceptingCallInterface { + cancelWithStatus(status: Status, details: string): void; + getPeer(): string; + start(metadata: Metadata, listener?: Partial): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sendMessageWithContext(context: MessageContext, message: any): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sendMessage(message: any): void; + startRead(): void; + halfClose(): void; +} + +export class InterceptingCall implements InterceptingCallInterface { + /** + * The requester that this InterceptingCall uses to modify outgoing operations + */ + private requester: FullRequester; + /** + * Indicates that metadata has been passed to the requester's start + * method but it has not been passed to the corresponding next callback + */ + private processingMetadata = false; + /** + * Message context for a pending message that is waiting for + */ + private pendingMessageContext: MessageContext | null = null; + private pendingMessage: any; + /** + * Indicates that a message has been passed to the requester's sendMessage + * method but it has not been passed to the corresponding next callback + */ + private processingMessage = false; + /** + * Indicates that a status was received but could not be propagated because + * a message was still being processed. + */ + private pendingHalfClose = false; + constructor( + private nextCall: InterceptingCallInterface, + requester?: Requester + ) { + if (requester) { + this.requester = { + start: requester.start ?? defaultRequester.start, + sendMessage: requester.sendMessage ?? defaultRequester.sendMessage, + halfClose: requester.halfClose ?? defaultRequester.halfClose, + cancel: requester.cancel ?? defaultRequester.cancel, + }; + } else { + this.requester = defaultRequester; + } + } + + cancelWithStatus(status: Status, details: string) { + this.requester.cancel(() => { + this.nextCall.cancelWithStatus(status, details); + }); + } + + getPeer() { + return this.nextCall.getPeer(); + } + + private processPendingMessage() { + if (this.pendingMessageContext) { + this.nextCall.sendMessageWithContext(this.pendingMessageContext, this.pendingMessage); + this.pendingMessageContext = null; + this.pendingMessage = null; + } + } + + private processPendingHalfClose() { + if (this.pendingHalfClose) { + this.nextCall.halfClose(); + } + } + + start( + metadata: Metadata, + interceptingListener?: Partial + ): void { + const fullInterceptingListener: InterceptingListener = { + onReceiveMetadata: + interceptingListener?.onReceiveMetadata?.bind(interceptingListener) ?? + ((metadata) => {}), + onReceiveMessage: + interceptingListener?.onReceiveMessage?.bind(interceptingListener) ?? + ((message) => {}), + onReceiveStatus: + interceptingListener?.onReceiveStatus?.bind(interceptingListener) ?? + ((status) => {}), + }; + this.processingMetadata = true; + this.requester.start(metadata, fullInterceptingListener, (md, listener) => { + this.processingMetadata = false; + let finalInterceptingListener: InterceptingListener; + if (isInterceptingListener(listener)) { + finalInterceptingListener = listener; + } else { + const fullListener: FullListener = { + onReceiveMetadata: + listener.onReceiveMetadata ?? defaultListener.onReceiveMetadata, + onReceiveMessage: + listener.onReceiveMessage ?? defaultListener.onReceiveMessage, + onReceiveStatus: + listener.onReceiveStatus ?? defaultListener.onReceiveStatus, + }; + finalInterceptingListener = new InterceptingListenerImpl( + fullListener, + fullInterceptingListener + ); + } + this.nextCall.start(md, finalInterceptingListener); + this.processPendingMessage(); + this.processPendingHalfClose(); + }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sendMessageWithContext(context: MessageContext, message: any): void { + this.processingMessage = true; + this.requester.sendMessage(message, (finalMessage) => { + this.processingMessage = false; + if (this.processingMetadata) { + this.pendingMessageContext = context; + this.pendingMessage = message; + } else { + this.nextCall.sendMessageWithContext(context, finalMessage); + this.processPendingHalfClose(); + } + }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sendMessage(message: any): void { + this.sendMessageWithContext({}, message); + } + startRead(): void { + this.nextCall.startRead(); + } + halfClose(): void { + this.requester.halfClose(() => { + if (this.processingMetadata || this.processingMessage) { + this.pendingHalfClose = true; + } else { + this.nextCall.halfClose(); + } + }); + } +} + +function getCall(channel: Channel, path: string, options: CallOptions): Call { + const deadline = options.deadline ?? Infinity; + const host = options.host; + const parent = options.parent ?? null; + const propagateFlags = options.propagate_flags; + const credentials = options.credentials; + const call = channel.createCall(path, deadline, host, parent, propagateFlags); + if (credentials) { + call.setCredentials(credentials); + } + return call; +} + +/** + * InterceptingCall implementation that directly owns the underlying Call + * object and handles serialization and deseraizliation. + */ +class BaseInterceptingCall implements InterceptingCallInterface { + constructor( + protected call: Call, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected methodDefinition: ClientMethodDefinition + ) {} + cancelWithStatus(status: Status, details: string): void { + this.call.cancelWithStatus(status, details); + } + getPeer(): string { + return this.call.getPeer(); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sendMessageWithContext(context: MessageContext, message: any): void { + let serialized: Buffer; + try { + serialized = this.methodDefinition.requestSerialize(message); + } catch (e) { + this.call.cancelWithStatus( + Status.INTERNAL, + `Request message serialization failure: ${getErrorMessage(e)}` + ); + return; + } + this.call.sendMessageWithContext(context, serialized); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sendMessage(message: any) { + this.sendMessageWithContext({}, message); + } + start( + metadata: Metadata, + interceptingListener?: Partial + ): void { + let readError: StatusObject | null = null; + this.call.start(metadata, { + onReceiveMetadata: (metadata) => { + interceptingListener?.onReceiveMetadata?.(metadata); + }, + onReceiveMessage: (message) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let deserialized: any; + try { + deserialized = this.methodDefinition.responseDeserialize(message); + } catch (e) { + readError = { + code: Status.INTERNAL, + details: `Response message parsing error: ${getErrorMessage(e)}`, + metadata: new Metadata(), + }; + this.call.cancelWithStatus(readError.code, readError.details); + return; + } + interceptingListener?.onReceiveMessage?.(deserialized); + }, + onReceiveStatus: (status) => { + if (readError) { + interceptingListener?.onReceiveStatus?.(readError); + } else { + interceptingListener?.onReceiveStatus?.(status); + } + }, + }); + } + startRead() { + this.call.startRead(); + } + halfClose(): void { + this.call.halfClose(); + } +} + +/** + * BaseInterceptingCall with special-cased behavior for methods with unary + * responses. + */ +class BaseUnaryInterceptingCall + extends BaseInterceptingCall + implements InterceptingCallInterface { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(call: Call, methodDefinition: ClientMethodDefinition) { + super(call, methodDefinition); + } + start(metadata: Metadata, listener?: Partial): void { + let receivedMessage = false; + const wrapperListener: InterceptingListener = { + onReceiveMetadata: + listener?.onReceiveMetadata?.bind(listener) ?? ((metadata) => {}), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage: (message: any) => { + receivedMessage = true; + listener?.onReceiveMessage?.(message); + }, + onReceiveStatus: (status: StatusObject) => { + if (!receivedMessage) { + listener?.onReceiveMessage?.(null); + } + listener?.onReceiveStatus?.(status); + }, + }; + super.start(metadata, wrapperListener); + this.call.startRead(); + } +} + +/** + * BaseInterceptingCall with special-cased behavior for methods with streaming + * responses. + */ +class BaseStreamingInterceptingCall + extends BaseInterceptingCall + implements InterceptingCallInterface {} + +function getBottomInterceptingCall( + channel: Channel, + options: InterceptorOptions, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + methodDefinition: ClientMethodDefinition +) { + const call = getCall(channel, methodDefinition.path, options); + if (methodDefinition.responseStream) { + return new BaseStreamingInterceptingCall(call, methodDefinition); + } else { + return new BaseUnaryInterceptingCall(call, methodDefinition); + } +} + +export interface NextCall { + (options: InterceptorOptions): InterceptingCallInterface; +} + +export interface Interceptor { + (options: InterceptorOptions, nextCall: NextCall): InterceptingCall; +} + +export interface InterceptorProvider { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (methodDefinition: ClientMethodDefinition): Interceptor; +} + +export interface InterceptorArguments { + clientInterceptors: Interceptor[]; + clientInterceptorProviders: InterceptorProvider[]; + callInterceptors: Interceptor[]; + callInterceptorProviders: InterceptorProvider[]; +} + +export function getInterceptingCall( + interceptorArgs: InterceptorArguments, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + methodDefinition: ClientMethodDefinition, + options: CallOptions, + channel: Channel +): InterceptingCallInterface { + if ( + interceptorArgs.clientInterceptors.length > 0 && + interceptorArgs.clientInterceptorProviders.length > 0 + ) { + throw new InterceptorConfigurationError( + 'Both interceptors and interceptor_providers were passed as options ' + + 'to the client constructor. Only one of these is allowed.' + ); + } + if ( + interceptorArgs.callInterceptors.length > 0 && + interceptorArgs.callInterceptorProviders.length > 0 + ) { + throw new InterceptorConfigurationError( + 'Both interceptors and interceptor_providers were passed as call ' + + 'options. Only one of these is allowed.' + ); + } + let interceptors: Interceptor[] = []; + // Interceptors passed to the call override interceptors passed to the client constructor + if ( + interceptorArgs.callInterceptors.length > 0 || + interceptorArgs.callInterceptorProviders.length > 0 + ) { + interceptors = ([] as Interceptor[]) + .concat( + interceptorArgs.callInterceptors, + interceptorArgs.callInterceptorProviders.map((provider) => + provider(methodDefinition) + ) + ) + .filter((interceptor) => interceptor); + // Filter out falsy values when providers return nothing + } else { + interceptors = ([] as Interceptor[]) + .concat( + interceptorArgs.clientInterceptors, + interceptorArgs.clientInterceptorProviders.map((provider) => + provider(methodDefinition) + ) + ) + .filter((interceptor) => interceptor); + // Filter out falsy values when providers return nothing + } + const interceptorOptions = Object.assign({}, options, { + method_definition: methodDefinition, + }); + /* For each interceptor in the list, the nextCall function passed to it is + * based on the next interceptor in the list, using a nextCall function + * constructed with the following interceptor in the list, and so on. The + * initialValue, which is effectively at the end of the list, is a nextCall + * function that invokes getBottomInterceptingCall, the result of which + * handles (de)serialization and also gets the underlying call from the + * channel. */ + const getCall: NextCall = interceptors.reduceRight( + (nextCall: NextCall, nextInterceptor: Interceptor) => { + return (currentOptions) => nextInterceptor(currentOptions, nextCall); + }, + (finalOptions: InterceptorOptions) => + getBottomInterceptingCall(channel, finalOptions, methodDefinition) + ); + return getCall(interceptorOptions); +} diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts new file mode 100644 index 000000000..7c856ee5b --- /dev/null +++ b/packages/grpc-js/src/client.ts @@ -0,0 +1,711 @@ +/* + * Copyright 2019 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. + * + */ + +import { + ClientDuplexStream, + ClientDuplexStreamImpl, + ClientReadableStream, + ClientReadableStreamImpl, + ClientUnaryCall, + ClientUnaryCallImpl, + ClientWritableStream, + ClientWritableStreamImpl, + ServiceError, + callErrorFromStatus, + SurfaceCall, +} from './call'; +import { CallCredentials } from './call-credentials'; +import { StatusObject } from './call-interface'; +import { Channel, ChannelImplementation } from './channel'; +import { ConnectivityState } from './connectivity-state'; +import { ChannelCredentials } from './channel-credentials'; +import { ChannelOptions } from './channel-options'; +import { Status } from './constants'; +import { Metadata } from './metadata'; +import { ClientMethodDefinition } from './make-client'; +import { + getInterceptingCall, + Interceptor, + InterceptorProvider, + InterceptorArguments, + InterceptingCallInterface, +} from './client-interceptors'; +import { + ServerUnaryCall, + ServerReadableStream, + ServerWritableStream, + ServerDuplexStream, +} from './server-call'; +import { Deadline } from './deadline'; + +const CHANNEL_SYMBOL = Symbol(); +const INTERCEPTOR_SYMBOL = Symbol(); +const INTERCEPTOR_PROVIDER_SYMBOL = Symbol(); +const CALL_INVOCATION_TRANSFORMER_SYMBOL = Symbol(); + +function isFunction( + arg: Metadata | CallOptions | UnaryCallback | undefined +): arg is UnaryCallback { + return typeof arg === 'function'; +} + +export interface UnaryCallback { + (err: ServiceError | null, value?: ResponseType): void; +} + +/* eslint-disable @typescript-eslint/no-explicit-any */ +export interface CallOptions { + deadline?: Deadline; + host?: string; + parent?: + | ServerUnaryCall + | ServerReadableStream + | ServerWritableStream + | ServerDuplexStream; + propagate_flags?: number; + credentials?: CallCredentials; + interceptors?: Interceptor[]; + interceptor_providers?: InterceptorProvider[]; +} +/* eslint-enable @typescript-eslint/no-explicit-any */ + +export interface CallProperties { + argument?: RequestType; + metadata: Metadata; + call: SurfaceCall; + channel: Channel; + methodDefinition: ClientMethodDefinition; + callOptions: CallOptions; + callback?: UnaryCallback; +} + +export interface CallInvocationTransformer { + (callProperties: CallProperties): CallProperties; // eslint-disable-line @typescript-eslint/no-explicit-any +} + +export type ClientOptions = Partial & { + channelOverride?: Channel; + channelFactoryOverride?: ( + address: string, + credentials: ChannelCredentials, + options: ClientOptions + ) => Channel; + interceptors?: Interceptor[]; + interceptor_providers?: InterceptorProvider[]; + callInvocationTransformer?: CallInvocationTransformer; +}; + +function getErrorStackString(error: Error): string { + return error.stack!.split('\n').slice(1).join('\n'); +} + +/** + * A generic gRPC client. Primarily useful as a base class for all generated + * clients. + */ +export class Client { + private readonly [CHANNEL_SYMBOL]: Channel; + private readonly [INTERCEPTOR_SYMBOL]: Interceptor[]; + private readonly [INTERCEPTOR_PROVIDER_SYMBOL]: InterceptorProvider[]; + private readonly [CALL_INVOCATION_TRANSFORMER_SYMBOL]?: CallInvocationTransformer; + constructor( + address: string, + credentials: ChannelCredentials, + options: ClientOptions = {} + ) { + options = Object.assign({}, options); + this[INTERCEPTOR_SYMBOL] = options.interceptors ?? []; + delete options.interceptors; + this[INTERCEPTOR_PROVIDER_SYMBOL] = options.interceptor_providers ?? []; + delete options.interceptor_providers; + if ( + this[INTERCEPTOR_SYMBOL].length > 0 && + this[INTERCEPTOR_PROVIDER_SYMBOL].length > 0 + ) { + throw new Error( + 'Both interceptors and interceptor_providers were passed as options ' + + 'to the client constructor. Only one of these is allowed.' + ); + } + this[CALL_INVOCATION_TRANSFORMER_SYMBOL] = + options.callInvocationTransformer; + delete options.callInvocationTransformer; + if (options.channelOverride) { + this[CHANNEL_SYMBOL] = options.channelOverride; + } else if (options.channelFactoryOverride) { + const channelFactoryOverride = options.channelFactoryOverride; + delete options.channelFactoryOverride; + this[CHANNEL_SYMBOL] = channelFactoryOverride( + address, + credentials, + options + ); + } else { + this[CHANNEL_SYMBOL] = new ChannelImplementation( + address, + credentials, + options + ); + } + } + + close(): void { + this[CHANNEL_SYMBOL].close(); + } + + getChannel(): Channel { + return this[CHANNEL_SYMBOL]; + } + + waitForReady(deadline: Deadline, callback: (error?: Error) => void): void { + const checkState = (err?: Error) => { + if (err) { + callback(new Error('Failed to connect before the deadline')); + return; + } + let newState; + try { + newState = this[CHANNEL_SYMBOL].getConnectivityState(true); + } catch (e) { + callback(new Error('The channel has been closed')); + return; + } + if (newState === ConnectivityState.READY) { + callback(); + } else { + try { + this[CHANNEL_SYMBOL].watchConnectivityState( + newState, + deadline, + checkState + ); + } catch (e) { + callback(new Error('The channel has been closed')); + } + } + }; + setImmediate(checkState); + } + + private checkOptionalUnaryResponseArguments( + arg1: Metadata | CallOptions | UnaryCallback, + arg2?: CallOptions | UnaryCallback, + arg3?: UnaryCallback + ): { + metadata: Metadata; + options: CallOptions; + callback: UnaryCallback; + } { + if (isFunction(arg1)) { + return { metadata: new Metadata(), options: {}, callback: arg1 }; + } else if (isFunction(arg2)) { + if (arg1 instanceof Metadata) { + return { metadata: arg1, options: {}, callback: arg2 }; + } else { + return { metadata: new Metadata(), options: arg1, callback: arg2 }; + } + } else { + if ( + !( + arg1 instanceof Metadata && + arg2 instanceof Object && + isFunction(arg3) + ) + ) { + throw new Error('Incorrect arguments passed'); + } + return { metadata: arg1, options: arg2, callback: arg3 }; + } + } + + makeUnaryRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + argument: RequestType, + metadata: Metadata, + options: CallOptions, + callback: UnaryCallback + ): ClientUnaryCall; + makeUnaryRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + argument: RequestType, + metadata: Metadata, + callback: UnaryCallback + ): ClientUnaryCall; + makeUnaryRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + argument: RequestType, + options: CallOptions, + callback: UnaryCallback + ): ClientUnaryCall; + makeUnaryRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + argument: RequestType, + callback: UnaryCallback + ): ClientUnaryCall; + makeUnaryRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + argument: RequestType, + metadata: Metadata | CallOptions | UnaryCallback, + options?: CallOptions | UnaryCallback, + callback?: UnaryCallback + ): ClientUnaryCall { + const checkedArguments = this.checkOptionalUnaryResponseArguments( + metadata, + options, + callback + ); + const methodDefinition: ClientMethodDefinition< + RequestType, + ResponseType + > = { + path: method, + requestStream: false, + responseStream: false, + requestSerialize: serialize, + responseDeserialize: deserialize, + }; + let callProperties: CallProperties = { + argument: argument, + metadata: checkedArguments.metadata, + call: new ClientUnaryCallImpl(), + channel: this[CHANNEL_SYMBOL], + methodDefinition: methodDefinition, + callOptions: checkedArguments.options, + callback: checkedArguments.callback, + }; + if (this[CALL_INVOCATION_TRANSFORMER_SYMBOL]) { + callProperties = this[CALL_INVOCATION_TRANSFORMER_SYMBOL]!( + callProperties + ) as CallProperties; + } + const emitter: ClientUnaryCall = callProperties.call; + const interceptorArgs: InterceptorArguments = { + clientInterceptors: this[INTERCEPTOR_SYMBOL], + clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], + callInterceptors: callProperties.callOptions.interceptors ?? [], + callInterceptorProviders: + callProperties.callOptions.interceptor_providers ?? [], + }; + const call: InterceptingCallInterface = getInterceptingCall( + interceptorArgs, + callProperties.methodDefinition, + callProperties.callOptions, + callProperties.channel + ); + /* This needs to happen before the emitter is used. Unfortunately we can't + * enforce this with the type system. We need to construct this emitter + * before calling the CallInvocationTransformer, and we need to create the + * call after that. */ + emitter.call = call; + let responseMessage: ResponseType | null = null; + let receivedStatus = false; + let callerStackError: Error | null = new Error(); + call.start(callProperties.metadata, { + onReceiveMetadata: (metadata) => { + emitter.emit('metadata', metadata); + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage(message: any) { + if (responseMessage !== null) { + call.cancelWithStatus(Status.INTERNAL, 'Too many responses received'); + } + responseMessage = message; + }, + onReceiveStatus(status: StatusObject) { + if (receivedStatus) { + return; + } + receivedStatus = true; + if (status.code === Status.OK) { + if (responseMessage === null) { + const callerStack = getErrorStackString(callerStackError!); + callProperties.callback!(callErrorFromStatus({ + code: Status.INTERNAL, + details: 'No message received', + metadata: status.metadata + }, callerStack)); + } else { + callProperties.callback!(null, responseMessage); + } + } else { + const callerStack = getErrorStackString(callerStackError!); + callProperties.callback!(callErrorFromStatus(status, callerStack)); + } + /* Avoid retaining the callerStackError object in the call context of + * the status event handler. */ + callerStackError = null; + emitter.emit('status', status); + }, + }); + call.sendMessage(argument); + call.halfClose(); + return emitter; + } + + makeClientStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + metadata: Metadata, + options: CallOptions, + callback: UnaryCallback + ): ClientWritableStream; + makeClientStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + metadata: Metadata, + callback: UnaryCallback + ): ClientWritableStream; + makeClientStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + options: CallOptions, + callback: UnaryCallback + ): ClientWritableStream; + makeClientStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + callback: UnaryCallback + ): ClientWritableStream; + makeClientStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + metadata: Metadata | CallOptions | UnaryCallback, + options?: CallOptions | UnaryCallback, + callback?: UnaryCallback + ): ClientWritableStream { + const checkedArguments = this.checkOptionalUnaryResponseArguments( + metadata, + options, + callback + ); + const methodDefinition: ClientMethodDefinition< + RequestType, + ResponseType + > = { + path: method, + requestStream: true, + responseStream: false, + requestSerialize: serialize, + responseDeserialize: deserialize, + }; + let callProperties: CallProperties = { + metadata: checkedArguments.metadata, + call: new ClientWritableStreamImpl(serialize), + channel: this[CHANNEL_SYMBOL], + methodDefinition: methodDefinition, + callOptions: checkedArguments.options, + callback: checkedArguments.callback, + }; + if (this[CALL_INVOCATION_TRANSFORMER_SYMBOL]) { + callProperties = this[CALL_INVOCATION_TRANSFORMER_SYMBOL]!( + callProperties + ) as CallProperties; + } + const emitter: ClientWritableStream = callProperties.call as ClientWritableStream; + const interceptorArgs: InterceptorArguments = { + clientInterceptors: this[INTERCEPTOR_SYMBOL], + clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], + callInterceptors: callProperties.callOptions.interceptors ?? [], + callInterceptorProviders: + callProperties.callOptions.interceptor_providers ?? [], + }; + const call: InterceptingCallInterface = getInterceptingCall( + interceptorArgs, + callProperties.methodDefinition, + callProperties.callOptions, + callProperties.channel + ); + /* This needs to happen before the emitter is used. Unfortunately we can't + * enforce this with the type system. We need to construct this emitter + * before calling the CallInvocationTransformer, and we need to create the + * call after that. */ + emitter.call = call; + let responseMessage: ResponseType | null = null; + let receivedStatus = false; + let callerStackError: Error | null = new Error(); + call.start(callProperties.metadata, { + onReceiveMetadata: (metadata) => { + emitter.emit('metadata', metadata); + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage(message: any) { + if (responseMessage !== null) { + call.cancelWithStatus(Status.INTERNAL, 'Too many responses received'); + } + responseMessage = message; + }, + onReceiveStatus(status: StatusObject) { + if (receivedStatus) { + return; + } + receivedStatus = true; + if (status.code === Status.OK) { + if (responseMessage === null) { + const callerStack = getErrorStackString(callerStackError!); + callProperties.callback!(callErrorFromStatus({ + code: Status.INTERNAL, + details: 'No message received', + metadata: status.metadata + }, callerStack)); + } else { + callProperties.callback!(null, responseMessage); + } + } else { + const callerStack = getErrorStackString(callerStackError!); + callProperties.callback!(callErrorFromStatus(status, callerStack)); + } + /* Avoid retaining the callerStackError object in the call context of + * the status event handler. */ + callerStackError = null; + emitter.emit('status', status); + }, + }); + return emitter; + } + + private checkMetadataAndOptions( + arg1?: Metadata | CallOptions, + arg2?: CallOptions + ): { metadata: Metadata; options: CallOptions } { + let metadata: Metadata; + let options: CallOptions; + if (arg1 instanceof Metadata) { + metadata = arg1; + if (arg2) { + options = arg2; + } else { + options = {}; + } + } else { + if (arg1) { + options = arg1; + } else { + options = {}; + } + metadata = new Metadata(); + } + return { metadata, options }; + } + + makeServerStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + argument: RequestType, + metadata: Metadata, + options?: CallOptions + ): ClientReadableStream; + makeServerStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + argument: RequestType, + options?: CallOptions + ): ClientReadableStream; + makeServerStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + argument: RequestType, + metadata?: Metadata | CallOptions, + options?: CallOptions + ): ClientReadableStream { + const checkedArguments = this.checkMetadataAndOptions(metadata, options); + const methodDefinition: ClientMethodDefinition< + RequestType, + ResponseType + > = { + path: method, + requestStream: false, + responseStream: true, + requestSerialize: serialize, + responseDeserialize: deserialize, + }; + let callProperties: CallProperties = { + argument: argument, + metadata: checkedArguments.metadata, + call: new ClientReadableStreamImpl(deserialize), + channel: this[CHANNEL_SYMBOL], + methodDefinition: methodDefinition, + callOptions: checkedArguments.options, + }; + if (this[CALL_INVOCATION_TRANSFORMER_SYMBOL]) { + callProperties = this[CALL_INVOCATION_TRANSFORMER_SYMBOL]!( + callProperties + ) as CallProperties; + } + const stream: ClientReadableStream = callProperties.call as ClientReadableStream; + const interceptorArgs: InterceptorArguments = { + clientInterceptors: this[INTERCEPTOR_SYMBOL], + clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], + callInterceptors: callProperties.callOptions.interceptors ?? [], + callInterceptorProviders: + callProperties.callOptions.interceptor_providers ?? [], + }; + const call: InterceptingCallInterface = getInterceptingCall( + interceptorArgs, + callProperties.methodDefinition, + callProperties.callOptions, + callProperties.channel + ); + /* This needs to happen before the emitter is used. Unfortunately we can't + * enforce this with the type system. We need to construct this emitter + * before calling the CallInvocationTransformer, and we need to create the + * call after that. */ + stream.call = call; + let receivedStatus = false; + let callerStackError: Error | null = new Error(); + call.start(callProperties.metadata, { + onReceiveMetadata(metadata: Metadata) { + stream.emit('metadata', metadata); + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage(message: any) { + stream.push(message); + }, + onReceiveStatus(status: StatusObject) { + if (receivedStatus) { + return; + } + receivedStatus = true; + stream.push(null); + if (status.code !== Status.OK) { + const callerStack = getErrorStackString(callerStackError!); + stream.emit('error', callErrorFromStatus(status, callerStack)); + } + /* Avoid retaining the callerStackError object in the call context of + * the status event handler. */ + callerStackError = null; + stream.emit('status', status); + }, + }); + call.sendMessage(argument); + call.halfClose(); + return stream; + } + + makeBidiStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + metadata: Metadata, + options?: CallOptions + ): ClientDuplexStream; + makeBidiStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + options?: CallOptions + ): ClientDuplexStream; + makeBidiStreamRequest( + method: string, + serialize: (value: RequestType) => Buffer, + deserialize: (value: Buffer) => ResponseType, + metadata?: Metadata | CallOptions, + options?: CallOptions + ): ClientDuplexStream { + const checkedArguments = this.checkMetadataAndOptions(metadata, options); + const methodDefinition: ClientMethodDefinition< + RequestType, + ResponseType + > = { + path: method, + requestStream: true, + responseStream: true, + requestSerialize: serialize, + responseDeserialize: deserialize, + }; + let callProperties: CallProperties = { + metadata: checkedArguments.metadata, + call: new ClientDuplexStreamImpl( + serialize, + deserialize + ), + channel: this[CHANNEL_SYMBOL], + methodDefinition: methodDefinition, + callOptions: checkedArguments.options, + }; + if (this[CALL_INVOCATION_TRANSFORMER_SYMBOL]) { + callProperties = this[CALL_INVOCATION_TRANSFORMER_SYMBOL]!( + callProperties + ) as CallProperties; + } + const stream: ClientDuplexStream< + RequestType, + ResponseType + > = callProperties.call as ClientDuplexStream; + const interceptorArgs: InterceptorArguments = { + clientInterceptors: this[INTERCEPTOR_SYMBOL], + clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], + callInterceptors: callProperties.callOptions.interceptors ?? [], + callInterceptorProviders: + callProperties.callOptions.interceptor_providers ?? [], + }; + const call: InterceptingCallInterface = getInterceptingCall( + interceptorArgs, + callProperties.methodDefinition, + callProperties.callOptions, + callProperties.channel + ); + /* This needs to happen before the emitter is used. Unfortunately we can't + * enforce this with the type system. We need to construct this emitter + * before calling the CallInvocationTransformer, and we need to create the + * call after that. */ + stream.call = call; + let receivedStatus = false; + let callerStackError: Error | null = new Error(); + call.start(callProperties.metadata, { + onReceiveMetadata(metadata: Metadata) { + stream.emit('metadata', metadata); + }, + onReceiveMessage(message: Buffer) { + stream.push(message); + }, + onReceiveStatus(status: StatusObject) { + if (receivedStatus) { + return; + } + receivedStatus = true; + stream.push(null); + if (status.code !== Status.OK) { + const callerStack = getErrorStackString(callerStackError!); + stream.emit('error', callErrorFromStatus(status, callerStack)); + } + /* Avoid retaining the callerStackError object in the call context of + * the status event handler. */ + callerStackError = null; + stream.emit('status', status); + }, + }); + return stream; + } +} diff --git a/packages/grpc-js/src/compression-algorithms.ts b/packages/grpc-js/src/compression-algorithms.ts new file mode 100644 index 000000000..ca2c7a624 --- /dev/null +++ b/packages/grpc-js/src/compression-algorithms.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2021 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. + * + */ + +export enum CompressionAlgorithms { + identity = 0, + deflate = 1, + gzip = 2 +}; diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts new file mode 100644 index 000000000..86af3a5f6 --- /dev/null +++ b/packages/grpc-js/src/compression-filter.ts @@ -0,0 +1,327 @@ +/* + * Copyright 2019 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. + * + */ + +import * as zlib from 'zlib'; + +import { WriteObject, WriteFlags } from './call-interface'; +import { Channel } from './channel'; +import { ChannelOptions } from './channel-options'; +import { CompressionAlgorithms } from './compression-algorithms'; +import { DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, LogVerbosity, Status } from './constants'; +import { BaseFilter, Filter, FilterFactory } from './filter'; +import * as logging from './logging'; +import { Metadata, MetadataValue } from './metadata'; + +const isCompressionAlgorithmKey = (key: number): key is CompressionAlgorithms => { + return typeof key === 'number' && typeof CompressionAlgorithms[key] === 'string'; +} + +type CompressionAlgorithm = keyof typeof CompressionAlgorithms; + +type SharedCompressionFilterConfig = { + serverSupportedEncodingHeader?: string; +}; + +abstract class CompressionHandler { + protected abstract compressMessage(message: Buffer): Promise; + protected abstract decompressMessage(data: Buffer): Promise; + /** + * @param message Raw uncompressed message bytes + * @param compress Indicates whether the message should be compressed + * @return Framed message, compressed if applicable + */ + async writeMessage(message: Buffer, compress: boolean): Promise { + let messageBuffer = message; + if (compress) { + messageBuffer = await this.compressMessage(messageBuffer); + } + const output = Buffer.allocUnsafe(messageBuffer.length + 5); + output.writeUInt8(compress ? 1 : 0, 0); + output.writeUInt32BE(messageBuffer.length, 1); + messageBuffer.copy(output, 5); + return output; + } + /** + * @param data Framed message, possibly compressed + * @return Uncompressed message + */ + async readMessage(data: Buffer): Promise { + const compressed = data.readUInt8(0) === 1; + let messageBuffer = data.slice(5); + if (compressed) { + messageBuffer = await this.decompressMessage(messageBuffer); + } + return messageBuffer; + } +} + +class IdentityHandler extends CompressionHandler { + async compressMessage(message: Buffer) { + return message; + } + + async writeMessage(message: Buffer, compress: boolean): Promise { + const output = Buffer.allocUnsafe(message.length + 5); + /* With "identity" compression, messages should always be marked as + * uncompressed */ + output.writeUInt8(0, 0); + output.writeUInt32BE(message.length, 1); + message.copy(output, 5); + return output; + } + + decompressMessage(message: Buffer): Promise { + return Promise.reject( + new Error( + 'Received compressed message but "grpc-encoding" header was identity' + ) + ); + } +} + +class DeflateHandler extends CompressionHandler { + constructor(private maxRecvMessageLength: number) { + super(); + } + + compressMessage(message: Buffer) { + return new Promise((resolve, reject) => { + zlib.deflate(message, (err, output) => { + if (err) { + reject(err); + } else { + resolve(output); + } + }); + }); + } + + decompressMessage(message: Buffer) { + return new Promise((resolve, reject) => { + let totalLength = 0; + const messageParts: Buffer[] = []; + const decompresser = zlib.createInflate(); + decompresser.on('data', (chunk: Buffer) => { + messageParts.push(chunk); + totalLength += chunk.byteLength; + if (this.maxRecvMessageLength !== -1 && totalLength > this.maxRecvMessageLength) { + decompresser.destroy(); + reject({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message that decompresses to a size larger than ${this.maxRecvMessageLength}` + }); + } + }); + decompresser.on('end', () => { + resolve(Buffer.concat(messageParts)); + }); + decompresser.write(message); + decompresser.end(); + }); + } +} + +class GzipHandler extends CompressionHandler { + constructor(private maxRecvMessageLength: number) { + super(); + } + + compressMessage(message: Buffer) { + return new Promise((resolve, reject) => { + zlib.gzip(message, (err, output) => { + if (err) { + reject(err); + } else { + resolve(output); + } + }); + }); + } + + decompressMessage(message: Buffer) { + return new Promise((resolve, reject) => { + let totalLength = 0; + const messageParts: Buffer[] = []; + const decompresser = zlib.createGunzip(); + decompresser.on('data', (chunk: Buffer) => { + messageParts.push(chunk); + totalLength += chunk.byteLength; + if (this.maxRecvMessageLength !== -1 && totalLength > this.maxRecvMessageLength) { + decompresser.destroy(); + reject({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message that decompresses to a size larger than ${this.maxRecvMessageLength}` + }); + } + }); + decompresser.on('end', () => { + resolve(Buffer.concat(messageParts)); + }); + decompresser.write(message); + decompresser.end(); + }); + } +} + +class UnknownHandler extends CompressionHandler { + constructor(private readonly compressionName: string) { + super(); + } + compressMessage(message: Buffer): Promise { + return Promise.reject( + new Error( + `Received message compressed with unsupported compression method ${this.compressionName}` + ) + ); + } + + decompressMessage(message: Buffer): Promise { + // This should be unreachable + return Promise.reject( + new Error(`Compression method not supported: ${this.compressionName}`) + ); + } +} + +function getCompressionHandler(compressionName: string, maxReceiveMessageSize: number): CompressionHandler { + switch (compressionName) { + case 'identity': + return new IdentityHandler(); + case 'deflate': + return new DeflateHandler(maxReceiveMessageSize); + case 'gzip': + return new GzipHandler(maxReceiveMessageSize); + default: + return new UnknownHandler(compressionName); + } +} + +export class CompressionFilter extends BaseFilter implements Filter { + private sendCompression: CompressionHandler = new IdentityHandler(); + private receiveCompression: CompressionHandler = new IdentityHandler(); + private currentCompressionAlgorithm: CompressionAlgorithm = 'identity'; + private maxReceiveMessageLength: number; + + constructor(channelOptions: ChannelOptions, private sharedFilterConfig: SharedCompressionFilterConfig) { + super(); + + const compressionAlgorithmKey = + channelOptions['grpc.default_compression_algorithm']; + this.maxReceiveMessageLength = channelOptions['grpc.max_receive_message_length'] ?? DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH + if (compressionAlgorithmKey !== undefined) { + if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { + const clientSelectedEncoding = CompressionAlgorithms[compressionAlgorithmKey] as CompressionAlgorithm; + const serverSupportedEncodings = sharedFilterConfig.serverSupportedEncodingHeader?.split(','); + /** + * There are two possible situations here: + * 1) We don't have any info yet from the server about what compression it supports + * In that case we should just use what the client tells us to use + * 2) We've previously received a response from the server including a grpc-accept-encoding header + * In that case we only want to use the encoding chosen by the client if the server supports it + */ + if (!serverSupportedEncodings || serverSupportedEncodings.includes(clientSelectedEncoding)) { + this.currentCompressionAlgorithm = clientSelectedEncoding; + this.sendCompression = getCompressionHandler( + this.currentCompressionAlgorithm, + -1 + ); + } + } else { + logging.log(LogVerbosity.ERROR, `Invalid value provided for grpc.default_compression_algorithm option: ${compressionAlgorithmKey}`); + } + } + } + + async sendMetadata(metadata: Promise): Promise { + const headers: Metadata = await metadata; + headers.set('grpc-accept-encoding', 'identity,deflate,gzip'); + headers.set('accept-encoding', 'identity'); + + // No need to send the header if it's "identity" - behavior is identical; save the bandwidth + if (this.currentCompressionAlgorithm === 'identity') { + headers.remove('grpc-encoding'); + } else { + headers.set('grpc-encoding', this.currentCompressionAlgorithm); + } + + return headers; + } + + receiveMetadata(metadata: Metadata): Metadata { + const receiveEncoding: MetadataValue[] = metadata.get('grpc-encoding'); + if (receiveEncoding.length > 0) { + const encoding: MetadataValue = receiveEncoding[0]; + if (typeof encoding === 'string') { + this.receiveCompression = getCompressionHandler(encoding, this.maxReceiveMessageLength); + } + } + metadata.remove('grpc-encoding'); + + /* Check to see if the compression we're using to send messages is supported by the server + * If not, reset the sendCompression filter and have it use the default IdentityHandler */ + const serverSupportedEncodingsHeader = metadata.get('grpc-accept-encoding')[0] as string | undefined; + if (serverSupportedEncodingsHeader) { + this.sharedFilterConfig.serverSupportedEncodingHeader = serverSupportedEncodingsHeader; + const serverSupportedEncodings = serverSupportedEncodingsHeader.split(','); + + if (!serverSupportedEncodings.includes(this.currentCompressionAlgorithm)) { + this.sendCompression = new IdentityHandler(); + this.currentCompressionAlgorithm = 'identity'; + } + } + metadata.remove('grpc-accept-encoding'); + return metadata; + } + + async sendMessage(message: Promise): Promise { + /* This filter is special. The input message is the bare message bytes, + * and the output is a framed and possibly compressed message. For this + * reason, this filter should be at the bottom of the filter stack */ + const resolvedMessage: WriteObject = await message; + let compress: boolean; + if (this.sendCompression instanceof IdentityHandler) { + compress = false; + } else { + compress = ((resolvedMessage.flags ?? 0) & WriteFlags.NoCompress) === 0; + } + + return { + message: await this.sendCompression.writeMessage( + resolvedMessage.message, + compress + ), + flags: resolvedMessage.flags, + }; + } + + async receiveMessage(message: Promise) { + /* This filter is also special. The input message is framed and possibly + * compressed, and the output message is deframed and uncompressed. So + * this is another reason that this filter should be at the bottom of the + * filter stack. */ + return this.receiveCompression.readMessage(await message); + } +} + +export class CompressionFilterFactory + implements FilterFactory { + private sharedFilterConfig: SharedCompressionFilterConfig = {}; + constructor(channel: Channel, private readonly options: ChannelOptions) {} + createFilter(): CompressionFilter { + return new CompressionFilter(this.options, this.sharedFilterConfig); + } +} diff --git a/packages/grpc-js/src/connectivity-state.ts b/packages/grpc-js/src/connectivity-state.ts new file mode 100644 index 000000000..560ab9c39 --- /dev/null +++ b/packages/grpc-js/src/connectivity-state.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2021 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. + * + */ + +export enum ConnectivityState { + IDLE, + CONNECTING, + READY, + TRANSIENT_FAILURE, + SHUTDOWN, +} diff --git a/packages/grpc-js/src/constants.ts b/packages/grpc-js/src/constants.ts new file mode 100644 index 000000000..865b24c94 --- /dev/null +++ b/packages/grpc-js/src/constants.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2019 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. + * + */ + +export enum Status { + OK = 0, + CANCELLED, + UNKNOWN, + INVALID_ARGUMENT, + DEADLINE_EXCEEDED, + NOT_FOUND, + ALREADY_EXISTS, + PERMISSION_DENIED, + RESOURCE_EXHAUSTED, + FAILED_PRECONDITION, + ABORTED, + OUT_OF_RANGE, + UNIMPLEMENTED, + INTERNAL, + UNAVAILABLE, + DATA_LOSS, + UNAUTHENTICATED, +} + +export enum LogVerbosity { + DEBUG = 0, + INFO, + ERROR, + NONE, +} + +/** + * NOTE: This enum is not currently used in any implemented API in this + * library. It is included only for type parity with the other implementation. + */ +export enum Propagate { + DEADLINE = 1, + CENSUS_STATS_CONTEXT = 2, + CENSUS_TRACING_CONTEXT = 4, + CANCELLATION = 8, + // https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/propagation_bits.h#L43 + DEFAULTS = 0xffff | + Propagate.DEADLINE | + Propagate.CENSUS_STATS_CONTEXT | + Propagate.CENSUS_TRACING_CONTEXT | + Propagate.CANCELLATION, +} + +// -1 means unlimited +export const DEFAULT_MAX_SEND_MESSAGE_LENGTH = -1; + +// 4 MB default +export const DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH = 4 * 1024 * 1024; diff --git a/packages/grpc-js/src/control-plane-status.ts b/packages/grpc-js/src/control-plane-status.ts new file mode 100644 index 000000000..808d695b2 --- /dev/null +++ b/packages/grpc-js/src/control-plane-status.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2022 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. + * + */ + +import { Status } from './constants'; + +const INAPPROPRIATE_CONTROL_PLANE_CODES: Status[] = [ + Status.OK, + Status.INVALID_ARGUMENT, + Status.NOT_FOUND, + Status.ALREADY_EXISTS, + Status.FAILED_PRECONDITION, + Status.ABORTED, + Status.OUT_OF_RANGE, + Status.DATA_LOSS +] + +export function restrictControlPlaneStatusCode(code: Status, details: string): {code: Status, details: string} { + if (INAPPROPRIATE_CONTROL_PLANE_CODES.includes(code)) { + return { + code: Status.INTERNAL, + details: `Invalid status from control plane: ${code} ${Status[code]} ${details}` + } + } else { + return {code, details}; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/deadline.ts b/packages/grpc-js/src/deadline.ts new file mode 100644 index 000000000..be1f3c3b0 --- /dev/null +++ b/packages/grpc-js/src/deadline.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2019 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. + * + */ + +export type Deadline = Date | number; + +export function minDeadline(...deadlineList: Deadline[]): Deadline { + let minValue = Infinity; + for (const deadline of deadlineList) { + const deadlineMsecs = + deadline instanceof Date ? deadline.getTime() : deadline; + if (deadlineMsecs < minValue) { + minValue = deadlineMsecs; + } + } + return minValue; +} + +const units: Array<[string, number]> = [ + ['m', 1], + ['S', 1000], + ['M', 60 * 1000], + ['H', 60 * 60 * 1000], +]; + +export function getDeadlineTimeoutString(deadline: Deadline) { + const now = new Date().getTime(); + if (deadline instanceof Date) { + deadline = deadline.getTime(); + } + const timeoutMs = Math.max(deadline - now, 0); + for (const [unit, factor] of units) { + const amount = timeoutMs / factor; + if (amount < 1e8) { + return String(Math.ceil(amount)) + unit; + } + } + throw new Error('Deadline is too far in the future') +} + +/** + * See https://nodejs.org/api/timers.html#settimeoutcallback-delay-args + * In particular, "When delay is larger than 2147483647 or less than 1, the + * delay will be set to 1. Non-integer delays are truncated to an integer." + * This number of milliseconds is almost 25 days. + */ +const MAX_TIMEOUT_TIME = 2147483647; + +/** + * Get the timeout value that should be passed to setTimeout now for the timer + * to end at the deadline. For any deadline before now, the timer should end + * immediately, represented by a value of 0. For any deadline more than + * MAX_TIMEOUT_TIME milliseconds in the future, a timer cannot be set that will + * end at that time, so it is treated as infinitely far in the future. + * @param deadline + * @returns + */ +export function getRelativeTimeout(deadline: Deadline) { + const deadlineMs = deadline instanceof Date ? deadline.getTime() : deadline; + const now = new Date().getTime(); + const timeout = deadlineMs - now; + if (timeout < 0) { + return 0; + } else if (timeout > MAX_TIMEOUT_TIME) { + return Infinity + } else { + return timeout; + } +} + +export function deadlineToString(deadline: Deadline): string { + if (deadline instanceof Date) { + return deadline.toISOString(); + } else { + const dateDeadline = new Date(deadline); + if (Number.isNaN(dateDeadline.getTime())) { + return '' + deadline; + } else { + return dateDeadline.toISOString(); + } + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/duration.ts b/packages/grpc-js/src/duration.ts new file mode 100644 index 000000000..278c9ae54 --- /dev/null +++ b/packages/grpc-js/src/duration.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2022 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. + * + */ + +export interface Duration { + seconds: number; + nanos: number; +} + +export function msToDuration(millis: number): Duration { + return { + seconds: (millis / 1000) | 0, + nanos: (millis % 1000) * 1_000_000 | 0 + }; +} + +export function durationToMs(duration: Duration): number { + return (duration.seconds * 1000 + duration.nanos / 1_000_000) | 0; +} + +export function isDuration(value: any): value is Duration { + return (typeof value.seconds === 'number') && (typeof value.nanos === 'number'); +} \ No newline at end of file diff --git a/packages/grpc-js/src/error.ts b/packages/grpc-js/src/error.ts new file mode 100644 index 000000000..b973128a7 --- /dev/null +++ b/packages/grpc-js/src/error.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 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. + * + */ + +export function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } else { + return String(error); + } +} + +export function getErrorCode(error: unknown): number | null { + if ( + typeof error === 'object' && + error !== null && + 'code' in error && + typeof (error as Record).code === 'number' + ) { + return (error as Record).code; + } else { + return null; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/events.ts b/packages/grpc-js/src/events.ts new file mode 100644 index 000000000..7718746d5 --- /dev/null +++ b/packages/grpc-js/src/events.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2019 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. + * + */ + +export interface EmitterAugmentation1 { + addListener(event: Name, listener: (arg1: Arg) => void): this; + emit(event: Name, arg1: Arg): boolean; + on(event: Name, listener: (arg1: Arg) => void): this; + once(event: Name, listener: (arg1: Arg) => void): this; + prependListener(event: Name, listener: (arg1: Arg) => void): this; + prependOnceListener(event: Name, listener: (arg1: Arg) => void): this; + removeListener(event: Name, listener: (arg1: Arg) => void): this; +} diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts new file mode 100644 index 000000000..a7c28219b --- /dev/null +++ b/packages/grpc-js/src/experimental.ts @@ -0,0 +1,39 @@ +export { trace, log } from './logging'; +export { + Resolver, + ResolverListener, + registerResolver, + ConfigSelector, +} from './resolver'; +export { GrpcUri, uriToString } from './uri-parser'; +export { Duration, durationToMs } from './duration'; +export { ServiceConfig, MethodConfig, RetryPolicy } from './service-config'; +export { BackoffTimeout } from './backoff-timeout'; +export { + LoadBalancer, + LoadBalancingConfig, + ChannelControlHelper, + createChildChannelControlHelper, + registerLoadBalancerType, + getFirstUsableConfig, + validateLoadBalancingConfig, +} from './load-balancer'; +export { + SubchannelAddress, + subchannelAddressToString, +} from './subchannel-address'; +export { ChildLoadBalancerHandler } from './load-balancer-child-handler'; +export { + Picker, + UnavailablePicker, + QueuePicker, + PickResult, + PickArgs, + PickResultType, +} from './picker'; +export { Call as CallStream } from './call-interface'; +export { Filter, BaseFilter, FilterFactory } from './filter'; +export { FilterStackFactory } from './filter-stack'; +export { registerAdminService } from './admin'; +export { SubchannelInterface, BaseSubchannelWrapper, ConnectivityStateListener } from './subchannel-interface'; +export { OutlierDetectionLoadBalancingConfig, SuccessRateEjectionConfig, FailurePercentageEjectionConfig } from './load-balancer-outlier-detection'; diff --git a/packages/grpc-js/src/filter-stack.ts b/packages/grpc-js/src/filter-stack.ts new file mode 100644 index 000000000..4733c8218 --- /dev/null +++ b/packages/grpc-js/src/filter-stack.ts @@ -0,0 +1,100 @@ +/* + * Copyright 2019 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. + * + */ + +import { StatusObject, WriteObject } from './call-interface'; +import { Filter, FilterFactory } from './filter'; +import { Metadata } from './metadata'; + +export class FilterStack implements Filter { + constructor(private readonly filters: Filter[]) {} + + sendMetadata(metadata: Promise): Promise { + let result: Promise = metadata; + + for (let i = 0; i < this.filters.length; i++) { + result = this.filters[i].sendMetadata(result); + } + + return result; + } + + receiveMetadata(metadata: Metadata) { + let result: Metadata = metadata; + + for (let i = this.filters.length - 1; i >= 0; i--) { + result = this.filters[i].receiveMetadata(result); + } + + return result; + } + + sendMessage(message: Promise): Promise { + let result: Promise = message; + + for (let i = 0; i < this.filters.length; i++) { + result = this.filters[i].sendMessage(result); + } + + return result; + } + + receiveMessage(message: Promise): Promise { + let result: Promise = message; + + for (let i = this.filters.length - 1; i >= 0; i--) { + result = this.filters[i].receiveMessage(result); + } + + return result; + } + + receiveTrailers(status: StatusObject): StatusObject { + let result: StatusObject = status; + + for (let i = this.filters.length - 1; i >= 0; i--) { + result = this.filters[i].receiveTrailers(result); + } + + return result; + } + + push(filters: Filter[]) { + this.filters.unshift(...filters); + } + + getFilters(): Filter[] { + return this.filters; + } +} + +export class FilterStackFactory implements FilterFactory { + constructor(private readonly factories: Array>) {} + + push(filterFactories: FilterFactory[]) { + this.factories.unshift(...filterFactories); + } + + clone(): FilterStackFactory { + return new FilterStackFactory([...this.factories]); + } + + createFilter(): FilterStack { + return new FilterStack( + this.factories.map((factory) => factory.createFilter()) + ); + } +} diff --git a/packages/grpc-js/src/filter.ts b/packages/grpc-js/src/filter.ts new file mode 100644 index 000000000..5313f91a8 --- /dev/null +++ b/packages/grpc-js/src/filter.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2019 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. + * + */ + +import { StatusObject, WriteObject } from './call-interface'; +import { Metadata } from './metadata'; + +/** + * Filter classes represent related per-call logic and state that is primarily + * used to modify incoming and outgoing data. All async filters can be + * rejected. The rejection error must be a StatusObject, and a rejection will + * cause the call to end with that status. + */ +export interface Filter { + sendMetadata(metadata: Promise): Promise; + + receiveMetadata(metadata: Metadata): Metadata; + + sendMessage(message: Promise): Promise; + + receiveMessage(message: Promise): Promise; + + receiveTrailers(status: StatusObject): StatusObject; +} + +export abstract class BaseFilter implements Filter { + async sendMetadata(metadata: Promise): Promise { + return metadata; + } + + receiveMetadata(metadata: Metadata): Metadata { + return metadata; + } + + async sendMessage(message: Promise): Promise { + return message; + } + + async receiveMessage(message: Promise): Promise { + return message; + } + + receiveTrailers(status: StatusObject): StatusObject { + return status; + } +} + +export interface FilterFactory { + createFilter(): T; +} diff --git a/packages/grpc-js/src/generated/channelz.ts b/packages/grpc-js/src/generated/channelz.ts new file mode 100644 index 000000000..367cf27f6 --- /dev/null +++ b/packages/grpc-js/src/generated/channelz.ts @@ -0,0 +1,73 @@ +import type * as grpc from '../index'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { ChannelzClient as _grpc_channelz_v1_ChannelzClient, ChannelzDefinition as _grpc_channelz_v1_ChannelzDefinition } from './grpc/channelz/v1/Channelz'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + FloatValue: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + StringValue: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + } + } + grpc: { + channelz: { + v1: { + Address: MessageTypeDefinition + Channel: MessageTypeDefinition + ChannelConnectivityState: MessageTypeDefinition + ChannelData: MessageTypeDefinition + ChannelRef: MessageTypeDefinition + ChannelTrace: MessageTypeDefinition + ChannelTraceEvent: MessageTypeDefinition + /** + * Channelz is a service exposed by gRPC servers that provides detailed debug + * information. + */ + Channelz: SubtypeConstructor & { service: _grpc_channelz_v1_ChannelzDefinition } + GetChannelRequest: MessageTypeDefinition + GetChannelResponse: MessageTypeDefinition + GetServerRequest: MessageTypeDefinition + GetServerResponse: MessageTypeDefinition + GetServerSocketsRequest: MessageTypeDefinition + GetServerSocketsResponse: MessageTypeDefinition + GetServersRequest: MessageTypeDefinition + GetServersResponse: MessageTypeDefinition + GetSocketRequest: MessageTypeDefinition + GetSocketResponse: MessageTypeDefinition + GetSubchannelRequest: MessageTypeDefinition + GetSubchannelResponse: MessageTypeDefinition + GetTopChannelsRequest: MessageTypeDefinition + GetTopChannelsResponse: MessageTypeDefinition + Security: MessageTypeDefinition + Server: MessageTypeDefinition + ServerData: MessageTypeDefinition + ServerRef: MessageTypeDefinition + Socket: MessageTypeDefinition + SocketData: MessageTypeDefinition + SocketOption: MessageTypeDefinition + SocketOptionLinger: MessageTypeDefinition + SocketOptionTcpInfo: MessageTypeDefinition + SocketOptionTimeout: MessageTypeDefinition + SocketRef: MessageTypeDefinition + Subchannel: MessageTypeDefinition + SubchannelRef: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js/src/generated/google/protobuf/Any.ts b/packages/grpc-js/src/generated/google/protobuf/Any.ts new file mode 100644 index 000000000..fcaa6724e --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Any.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { AnyExtension } from '@grpc/proto-loader'; + +export type Any = AnyExtension | { + type_url: string; + value: Buffer | Uint8Array | string; +} + +export interface Any__Output { + 'type_url': (string); + 'value': (Buffer); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/BoolValue.ts b/packages/grpc-js/src/generated/google/protobuf/BoolValue.ts new file mode 100644 index 000000000..86507eaf1 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/BoolValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface BoolValue { + 'value'?: (boolean); +} + +export interface BoolValue__Output { + 'value': (boolean); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/BytesValue.ts b/packages/grpc-js/src/generated/google/protobuf/BytesValue.ts new file mode 100644 index 000000000..9cec76f71 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/BytesValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface BytesValue { + 'value'?: (Buffer | Uint8Array | string); +} + +export interface BytesValue__Output { + 'value': (Buffer); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/DoubleValue.ts b/packages/grpc-js/src/generated/google/protobuf/DoubleValue.ts new file mode 100644 index 000000000..d70b303c2 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/DoubleValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface DoubleValue { + 'value'?: (number | string); +} + +export interface DoubleValue__Output { + 'value': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/Duration.ts b/packages/grpc-js/src/generated/google/protobuf/Duration.ts new file mode 100644 index 000000000..8595377a0 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Duration.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Duration { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface Duration__Output { + 'seconds': (string); + 'nanos': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/FloatValue.ts b/packages/grpc-js/src/generated/google/protobuf/FloatValue.ts new file mode 100644 index 000000000..54a655fbb --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/FloatValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface FloatValue { + 'value'?: (number | string); +} + +export interface FloatValue__Output { + 'value': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/Int32Value.ts b/packages/grpc-js/src/generated/google/protobuf/Int32Value.ts new file mode 100644 index 000000000..ec4eeb7ec --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Int32Value.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface Int32Value { + 'value'?: (number); +} + +export interface Int32Value__Output { + 'value': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/Int64Value.ts b/packages/grpc-js/src/generated/google/protobuf/Int64Value.ts new file mode 100644 index 000000000..f7375196d --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Int64Value.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Int64Value { + 'value'?: (number | string | Long); +} + +export interface Int64Value__Output { + 'value': (string); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/StringValue.ts b/packages/grpc-js/src/generated/google/protobuf/StringValue.ts new file mode 100644 index 000000000..673090e3f --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/StringValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface StringValue { + 'value'?: (string); +} + +export interface StringValue__Output { + 'value': (string); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/Timestamp.ts b/packages/grpc-js/src/generated/google/protobuf/Timestamp.ts new file mode 100644 index 000000000..ceaa32b5f --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Timestamp.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Timestamp { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface Timestamp__Output { + 'seconds': (string); + 'nanos': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/UInt32Value.ts b/packages/grpc-js/src/generated/google/protobuf/UInt32Value.ts new file mode 100644 index 000000000..973ab34a5 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/UInt32Value.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface UInt32Value { + 'value'?: (number); +} + +export interface UInt32Value__Output { + 'value': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/UInt64Value.ts b/packages/grpc-js/src/generated/google/protobuf/UInt64Value.ts new file mode 100644 index 000000000..7a85c39ce --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/UInt64Value.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface UInt64Value { + 'value'?: (number | string | Long); +} + +export interface UInt64Value__Output { + 'value': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Address.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Address.ts new file mode 100644 index 000000000..259cfeabe --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Address.ts @@ -0,0 +1,89 @@ +// Original file: proto/channelz.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * An address type not included above. + */ +export interface _grpc_channelz_v1_Address_OtherAddress { + /** + * The human readable version of the value. This value should be set. + */ + 'name'?: (string); + /** + * The actual address message. + */ + 'value'?: (_google_protobuf_Any | null); +} + +/** + * An address type not included above. + */ +export interface _grpc_channelz_v1_Address_OtherAddress__Output { + /** + * The human readable version of the value. This value should be set. + */ + 'name': (string); + /** + * The actual address message. + */ + 'value': (_google_protobuf_Any__Output | null); +} + +export interface _grpc_channelz_v1_Address_TcpIpAddress { + /** + * Either the IPv4 or IPv6 address in bytes. Will be either 4 bytes or 16 + * bytes in length. + */ + 'ip_address'?: (Buffer | Uint8Array | string); + /** + * 0-64k, or -1 if not appropriate. + */ + 'port'?: (number); +} + +export interface _grpc_channelz_v1_Address_TcpIpAddress__Output { + /** + * Either the IPv4 or IPv6 address in bytes. Will be either 4 bytes or 16 + * bytes in length. + */ + 'ip_address': (Buffer); + /** + * 0-64k, or -1 if not appropriate. + */ + 'port': (number); +} + +/** + * A Unix Domain Socket address. + */ +export interface _grpc_channelz_v1_Address_UdsAddress { + 'filename'?: (string); +} + +/** + * A Unix Domain Socket address. + */ +export interface _grpc_channelz_v1_Address_UdsAddress__Output { + 'filename': (string); +} + +/** + * Address represents the address used to create the socket. + */ +export interface Address { + 'tcpip_address'?: (_grpc_channelz_v1_Address_TcpIpAddress | null); + 'uds_address'?: (_grpc_channelz_v1_Address_UdsAddress | null); + 'other_address'?: (_grpc_channelz_v1_Address_OtherAddress | null); + 'address'?: "tcpip_address"|"uds_address"|"other_address"; +} + +/** + * Address represents the address used to create the socket. + */ +export interface Address__Output { + 'tcpip_address'?: (_grpc_channelz_v1_Address_TcpIpAddress__Output | null); + 'uds_address'?: (_grpc_channelz_v1_Address_UdsAddress__Output | null); + 'other_address'?: (_grpc_channelz_v1_Address_OtherAddress__Output | null); + 'address': "tcpip_address"|"uds_address"|"other_address"; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Channel.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Channel.ts new file mode 100644 index 000000000..93b4a261d --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Channel.ts @@ -0,0 +1,68 @@ +// Original file: proto/channelz.proto + +import type { ChannelRef as _grpc_channelz_v1_ChannelRef, ChannelRef__Output as _grpc_channelz_v1_ChannelRef__Output } from '../../../grpc/channelz/v1/ChannelRef'; +import type { ChannelData as _grpc_channelz_v1_ChannelData, ChannelData__Output as _grpc_channelz_v1_ChannelData__Output } from '../../../grpc/channelz/v1/ChannelData'; +import type { SubchannelRef as _grpc_channelz_v1_SubchannelRef, SubchannelRef__Output as _grpc_channelz_v1_SubchannelRef__Output } from '../../../grpc/channelz/v1/SubchannelRef'; +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; + +/** + * Channel is a logical grouping of channels, subchannels, and sockets. + */ +export interface Channel { + /** + * The identifier for this channel. This should bet set. + */ + 'ref'?: (_grpc_channelz_v1_ChannelRef | null); + /** + * Data specific to this channel. + */ + 'data'?: (_grpc_channelz_v1_ChannelData | null); + /** + * There are no ordering guarantees on the order of channel refs. + * There may not be cycles in the ref graph. + * A channel ref may be present in more than one channel or subchannel. + */ + 'channel_ref'?: (_grpc_channelz_v1_ChannelRef)[]; + /** + * At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + * There are no ordering guarantees on the order of subchannel refs. + * There may not be cycles in the ref graph. + * A sub channel ref may be present in more than one channel or subchannel. + */ + 'subchannel_ref'?: (_grpc_channelz_v1_SubchannelRef)[]; + /** + * There are no ordering guarantees on the order of sockets. + */ + 'socket_ref'?: (_grpc_channelz_v1_SocketRef)[]; +} + +/** + * Channel is a logical grouping of channels, subchannels, and sockets. + */ +export interface Channel__Output { + /** + * The identifier for this channel. This should bet set. + */ + 'ref': (_grpc_channelz_v1_ChannelRef__Output | null); + /** + * Data specific to this channel. + */ + 'data': (_grpc_channelz_v1_ChannelData__Output | null); + /** + * There are no ordering guarantees on the order of channel refs. + * There may not be cycles in the ref graph. + * A channel ref may be present in more than one channel or subchannel. + */ + 'channel_ref': (_grpc_channelz_v1_ChannelRef__Output)[]; + /** + * At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + * There are no ordering guarantees on the order of subchannel refs. + * There may not be cycles in the ref graph. + * A sub channel ref may be present in more than one channel or subchannel. + */ + 'subchannel_ref': (_grpc_channelz_v1_SubchannelRef__Output)[]; + /** + * There are no ordering guarantees on the order of sockets. + */ + 'socket_ref': (_grpc_channelz_v1_SocketRef__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelConnectivityState.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelConnectivityState.ts new file mode 100644 index 000000000..be34ab98d --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelConnectivityState.ts @@ -0,0 +1,29 @@ +// Original file: proto/channelz.proto + + +// Original file: proto/channelz.proto + +export enum _grpc_channelz_v1_ChannelConnectivityState_State { + UNKNOWN = 0, + IDLE = 1, + CONNECTING = 2, + READY = 3, + TRANSIENT_FAILURE = 4, + SHUTDOWN = 5, +} + +/** + * These come from the specified states in this document: + * https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md + */ +export interface ChannelConnectivityState { + 'state'?: (_grpc_channelz_v1_ChannelConnectivityState_State | keyof typeof _grpc_channelz_v1_ChannelConnectivityState_State); +} + +/** + * These come from the specified states in this document: + * https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md + */ +export interface ChannelConnectivityState__Output { + 'state': (keyof typeof _grpc_channelz_v1_ChannelConnectivityState_State); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelData.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelData.ts new file mode 100644 index 000000000..6d6824af4 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelData.ts @@ -0,0 +1,76 @@ +// Original file: proto/channelz.proto + +import type { ChannelConnectivityState as _grpc_channelz_v1_ChannelConnectivityState, ChannelConnectivityState__Output as _grpc_channelz_v1_ChannelConnectivityState__Output } from '../../../grpc/channelz/v1/ChannelConnectivityState'; +import type { ChannelTrace as _grpc_channelz_v1_ChannelTrace, ChannelTrace__Output as _grpc_channelz_v1_ChannelTrace__Output } from '../../../grpc/channelz/v1/ChannelTrace'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Channel data is data related to a specific Channel or Subchannel. + */ +export interface ChannelData { + /** + * The connectivity state of the channel or subchannel. Implementations + * should always set this. + */ + 'state'?: (_grpc_channelz_v1_ChannelConnectivityState | null); + /** + * The target this channel originally tried to connect to. May be absent + */ + 'target'?: (string); + /** + * A trace of recent events on the channel. May be absent. + */ + 'trace'?: (_grpc_channelz_v1_ChannelTrace | null); + /** + * The number of calls started on the channel + */ + 'calls_started'?: (number | string | Long); + /** + * The number of calls that have completed with an OK status + */ + 'calls_succeeded'?: (number | string | Long); + /** + * The number of calls that have completed with a non-OK status + */ + 'calls_failed'?: (number | string | Long); + /** + * The last time a call was started on the channel. + */ + 'last_call_started_timestamp'?: (_google_protobuf_Timestamp | null); +} + +/** + * Channel data is data related to a specific Channel or Subchannel. + */ +export interface ChannelData__Output { + /** + * The connectivity state of the channel or subchannel. Implementations + * should always set this. + */ + 'state': (_grpc_channelz_v1_ChannelConnectivityState__Output | null); + /** + * The target this channel originally tried to connect to. May be absent + */ + 'target': (string); + /** + * A trace of recent events on the channel. May be absent. + */ + 'trace': (_grpc_channelz_v1_ChannelTrace__Output | null); + /** + * The number of calls started on the channel + */ + 'calls_started': (string); + /** + * The number of calls that have completed with an OK status + */ + 'calls_succeeded': (string); + /** + * The number of calls that have completed with a non-OK status + */ + 'calls_failed': (string); + /** + * The last time a call was started on the channel. + */ + 'last_call_started_timestamp': (_google_protobuf_Timestamp__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelRef.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelRef.ts new file mode 100644 index 000000000..231d00876 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelRef.ts @@ -0,0 +1,31 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * ChannelRef is a reference to a Channel. + */ +export interface ChannelRef { + /** + * The globally unique id for this channel. Must be a positive number. + */ + 'channel_id'?: (number | string | Long); + /** + * An optional name associated with the channel. + */ + 'name'?: (string); +} + +/** + * ChannelRef is a reference to a Channel. + */ +export interface ChannelRef__Output { + /** + * The globally unique id for this channel. Must be a positive number. + */ + 'channel_id': (string); + /** + * An optional name associated with the channel. + */ + 'name': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTrace.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTrace.ts new file mode 100644 index 000000000..7dbc8d924 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTrace.ts @@ -0,0 +1,45 @@ +// Original file: proto/channelz.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { ChannelTraceEvent as _grpc_channelz_v1_ChannelTraceEvent, ChannelTraceEvent__Output as _grpc_channelz_v1_ChannelTraceEvent__Output } from '../../../grpc/channelz/v1/ChannelTraceEvent'; +import type { Long } from '@grpc/proto-loader'; + +/** + * ChannelTrace represents the recent events that have occurred on the channel. + */ +export interface ChannelTrace { + /** + * Number of events ever logged in this tracing object. This can differ from + * events.size() because events can be overwritten or garbage collected by + * implementations. + */ + 'num_events_logged'?: (number | string | Long); + /** + * Time that this channel was created. + */ + 'creation_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * List of events that have occurred on this channel. + */ + 'events'?: (_grpc_channelz_v1_ChannelTraceEvent)[]; +} + +/** + * ChannelTrace represents the recent events that have occurred on the channel. + */ +export interface ChannelTrace__Output { + /** + * Number of events ever logged in this tracing object. This can differ from + * events.size() because events can be overwritten or garbage collected by + * implementations. + */ + 'num_events_logged': (string); + /** + * Time that this channel was created. + */ + 'creation_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * List of events that have occurred on this channel. + */ + 'events': (_grpc_channelz_v1_ChannelTraceEvent__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTraceEvent.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTraceEvent.ts new file mode 100644 index 000000000..24b97fbcd --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTraceEvent.ts @@ -0,0 +1,73 @@ +// Original file: proto/channelz.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { ChannelRef as _grpc_channelz_v1_ChannelRef, ChannelRef__Output as _grpc_channelz_v1_ChannelRef__Output } from '../../../grpc/channelz/v1/ChannelRef'; +import type { SubchannelRef as _grpc_channelz_v1_SubchannelRef, SubchannelRef__Output as _grpc_channelz_v1_SubchannelRef__Output } from '../../../grpc/channelz/v1/SubchannelRef'; + +// Original file: proto/channelz.proto + +/** + * The supported severity levels of trace events. + */ +export enum _grpc_channelz_v1_ChannelTraceEvent_Severity { + CT_UNKNOWN = 0, + CT_INFO = 1, + CT_WARNING = 2, + CT_ERROR = 3, +} + +/** + * A trace event is an interesting thing that happened to a channel or + * subchannel, such as creation, address resolution, subchannel creation, etc. + */ +export interface ChannelTraceEvent { + /** + * High level description of the event. + */ + 'description'?: (string); + /** + * the severity of the trace event + */ + 'severity'?: (_grpc_channelz_v1_ChannelTraceEvent_Severity | keyof typeof _grpc_channelz_v1_ChannelTraceEvent_Severity); + /** + * When this event occurred. + */ + 'timestamp'?: (_google_protobuf_Timestamp | null); + 'channel_ref'?: (_grpc_channelz_v1_ChannelRef | null); + 'subchannel_ref'?: (_grpc_channelz_v1_SubchannelRef | null); + /** + * ref of referenced channel or subchannel. + * Optional, only present if this event refers to a child object. For example, + * this field would be filled if this trace event was for a subchannel being + * created. + */ + 'child_ref'?: "channel_ref"|"subchannel_ref"; +} + +/** + * A trace event is an interesting thing that happened to a channel or + * subchannel, such as creation, address resolution, subchannel creation, etc. + */ +export interface ChannelTraceEvent__Output { + /** + * High level description of the event. + */ + 'description': (string); + /** + * the severity of the trace event + */ + 'severity': (keyof typeof _grpc_channelz_v1_ChannelTraceEvent_Severity); + /** + * When this event occurred. + */ + 'timestamp': (_google_protobuf_Timestamp__Output | null); + 'channel_ref'?: (_grpc_channelz_v1_ChannelRef__Output | null); + 'subchannel_ref'?: (_grpc_channelz_v1_SubchannelRef__Output | null); + /** + * ref of referenced channel or subchannel. + * Optional, only present if this event refers to a child object. For example, + * this field would be filled if this trace event was for a subchannel being + * created. + */ + 'child_ref': "channel_ref"|"subchannel_ref"; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts new file mode 100644 index 000000000..4c8c18aa7 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts @@ -0,0 +1,178 @@ +// Original file: proto/channelz.proto + +import type * as grpc from '../../../../index' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { GetChannelRequest as _grpc_channelz_v1_GetChannelRequest, GetChannelRequest__Output as _grpc_channelz_v1_GetChannelRequest__Output } from '../../../grpc/channelz/v1/GetChannelRequest'; +import type { GetChannelResponse as _grpc_channelz_v1_GetChannelResponse, GetChannelResponse__Output as _grpc_channelz_v1_GetChannelResponse__Output } from '../../../grpc/channelz/v1/GetChannelResponse'; +import type { GetServerRequest as _grpc_channelz_v1_GetServerRequest, GetServerRequest__Output as _grpc_channelz_v1_GetServerRequest__Output } from '../../../grpc/channelz/v1/GetServerRequest'; +import type { GetServerResponse as _grpc_channelz_v1_GetServerResponse, GetServerResponse__Output as _grpc_channelz_v1_GetServerResponse__Output } from '../../../grpc/channelz/v1/GetServerResponse'; +import type { GetServerSocketsRequest as _grpc_channelz_v1_GetServerSocketsRequest, GetServerSocketsRequest__Output as _grpc_channelz_v1_GetServerSocketsRequest__Output } from '../../../grpc/channelz/v1/GetServerSocketsRequest'; +import type { GetServerSocketsResponse as _grpc_channelz_v1_GetServerSocketsResponse, GetServerSocketsResponse__Output as _grpc_channelz_v1_GetServerSocketsResponse__Output } from '../../../grpc/channelz/v1/GetServerSocketsResponse'; +import type { GetServersRequest as _grpc_channelz_v1_GetServersRequest, GetServersRequest__Output as _grpc_channelz_v1_GetServersRequest__Output } from '../../../grpc/channelz/v1/GetServersRequest'; +import type { GetServersResponse as _grpc_channelz_v1_GetServersResponse, GetServersResponse__Output as _grpc_channelz_v1_GetServersResponse__Output } from '../../../grpc/channelz/v1/GetServersResponse'; +import type { GetSocketRequest as _grpc_channelz_v1_GetSocketRequest, GetSocketRequest__Output as _grpc_channelz_v1_GetSocketRequest__Output } from '../../../grpc/channelz/v1/GetSocketRequest'; +import type { GetSocketResponse as _grpc_channelz_v1_GetSocketResponse, GetSocketResponse__Output as _grpc_channelz_v1_GetSocketResponse__Output } from '../../../grpc/channelz/v1/GetSocketResponse'; +import type { GetSubchannelRequest as _grpc_channelz_v1_GetSubchannelRequest, GetSubchannelRequest__Output as _grpc_channelz_v1_GetSubchannelRequest__Output } from '../../../grpc/channelz/v1/GetSubchannelRequest'; +import type { GetSubchannelResponse as _grpc_channelz_v1_GetSubchannelResponse, GetSubchannelResponse__Output as _grpc_channelz_v1_GetSubchannelResponse__Output } from '../../../grpc/channelz/v1/GetSubchannelResponse'; +import type { GetTopChannelsRequest as _grpc_channelz_v1_GetTopChannelsRequest, GetTopChannelsRequest__Output as _grpc_channelz_v1_GetTopChannelsRequest__Output } from '../../../grpc/channelz/v1/GetTopChannelsRequest'; +import type { GetTopChannelsResponse as _grpc_channelz_v1_GetTopChannelsResponse, GetTopChannelsResponse__Output as _grpc_channelz_v1_GetTopChannelsResponse__Output } from '../../../grpc/channelz/v1/GetTopChannelsResponse'; + +/** + * Channelz is a service exposed by gRPC servers that provides detailed debug + * information. + */ +export interface ChannelzClient extends grpc.Client { + /** + * Returns a single Channel, or else a NOT_FOUND code. + */ + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + + /** + * Returns a single Server, or else a NOT_FOUND code. + */ + GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + /** + * Returns a single Server, or else a NOT_FOUND code. + */ + getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + + /** + * Gets all server sockets that exist in the process. + */ + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + /** + * Gets all server sockets that exist in the process. + */ + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + + /** + * Gets all servers that exist in the process. + */ + GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + /** + * Gets all servers that exist in the process. + */ + getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + + /** + * Returns a single Socket or else a NOT_FOUND code. + */ + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + /** + * Returns a single Socket or else a NOT_FOUND code. + */ + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + + /** + * Returns a single Subchannel, or else a NOT_FOUND code. + */ + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + /** + * Returns a single Subchannel, or else a NOT_FOUND code. + */ + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + + /** + * Gets all root channels (i.e. channels the application has directly + * created). This does not include subchannels nor non-top level channels. + */ + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + /** + * Gets all root channels (i.e. channels the application has directly + * created). This does not include subchannels nor non-top level channels. + */ + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + +} + +/** + * Channelz is a service exposed by gRPC servers that provides detailed debug + * information. + */ +export interface ChannelzHandlers extends grpc.UntypedServiceImplementation { + /** + * Returns a single Channel, or else a NOT_FOUND code. + */ + GetChannel: grpc.handleUnaryCall<_grpc_channelz_v1_GetChannelRequest__Output, _grpc_channelz_v1_GetChannelResponse>; + + /** + * Returns a single Server, or else a NOT_FOUND code. + */ + GetServer: grpc.handleUnaryCall<_grpc_channelz_v1_GetServerRequest__Output, _grpc_channelz_v1_GetServerResponse>; + + /** + * Gets all server sockets that exist in the process. + */ + GetServerSockets: grpc.handleUnaryCall<_grpc_channelz_v1_GetServerSocketsRequest__Output, _grpc_channelz_v1_GetServerSocketsResponse>; + + /** + * Gets all servers that exist in the process. + */ + GetServers: grpc.handleUnaryCall<_grpc_channelz_v1_GetServersRequest__Output, _grpc_channelz_v1_GetServersResponse>; + + /** + * Returns a single Socket or else a NOT_FOUND code. + */ + GetSocket: grpc.handleUnaryCall<_grpc_channelz_v1_GetSocketRequest__Output, _grpc_channelz_v1_GetSocketResponse>; + + /** + * Returns a single Subchannel, or else a NOT_FOUND code. + */ + GetSubchannel: grpc.handleUnaryCall<_grpc_channelz_v1_GetSubchannelRequest__Output, _grpc_channelz_v1_GetSubchannelResponse>; + + /** + * Gets all root channels (i.e. channels the application has directly + * created). This does not include subchannels nor non-top level channels. + */ + GetTopChannels: grpc.handleUnaryCall<_grpc_channelz_v1_GetTopChannelsRequest__Output, _grpc_channelz_v1_GetTopChannelsResponse>; + +} + +export interface ChannelzDefinition extends grpc.ServiceDefinition { + GetChannel: MethodDefinition<_grpc_channelz_v1_GetChannelRequest, _grpc_channelz_v1_GetChannelResponse, _grpc_channelz_v1_GetChannelRequest__Output, _grpc_channelz_v1_GetChannelResponse__Output> + GetServer: MethodDefinition<_grpc_channelz_v1_GetServerRequest, _grpc_channelz_v1_GetServerResponse, _grpc_channelz_v1_GetServerRequest__Output, _grpc_channelz_v1_GetServerResponse__Output> + GetServerSockets: MethodDefinition<_grpc_channelz_v1_GetServerSocketsRequest, _grpc_channelz_v1_GetServerSocketsResponse, _grpc_channelz_v1_GetServerSocketsRequest__Output, _grpc_channelz_v1_GetServerSocketsResponse__Output> + GetServers: MethodDefinition<_grpc_channelz_v1_GetServersRequest, _grpc_channelz_v1_GetServersResponse, _grpc_channelz_v1_GetServersRequest__Output, _grpc_channelz_v1_GetServersResponse__Output> + GetSocket: MethodDefinition<_grpc_channelz_v1_GetSocketRequest, _grpc_channelz_v1_GetSocketResponse, _grpc_channelz_v1_GetSocketRequest__Output, _grpc_channelz_v1_GetSocketResponse__Output> + GetSubchannel: MethodDefinition<_grpc_channelz_v1_GetSubchannelRequest, _grpc_channelz_v1_GetSubchannelResponse, _grpc_channelz_v1_GetSubchannelRequest__Output, _grpc_channelz_v1_GetSubchannelResponse__Output> + GetTopChannels: MethodDefinition<_grpc_channelz_v1_GetTopChannelsRequest, _grpc_channelz_v1_GetTopChannelsResponse, _grpc_channelz_v1_GetTopChannelsRequest__Output, _grpc_channelz_v1_GetTopChannelsResponse__Output> +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelRequest.ts new file mode 100644 index 000000000..437e2d60a --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelRequest.ts @@ -0,0 +1,17 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetChannelRequest { + /** + * channel_id is the identifier of the specific channel to get. + */ + 'channel_id'?: (number | string | Long); +} + +export interface GetChannelRequest__Output { + /** + * channel_id is the identifier of the specific channel to get. + */ + 'channel_id': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelResponse.ts new file mode 100644 index 000000000..2e967a458 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelResponse.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Channel as _grpc_channelz_v1_Channel, Channel__Output as _grpc_channelz_v1_Channel__Output } from '../../../grpc/channelz/v1/Channel'; + +export interface GetChannelResponse { + /** + * The Channel that corresponds to the requested channel_id. This field + * should be set. + */ + 'channel'?: (_grpc_channelz_v1_Channel | null); +} + +export interface GetChannelResponse__Output { + /** + * The Channel that corresponds to the requested channel_id. This field + * should be set. + */ + 'channel': (_grpc_channelz_v1_Channel__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerRequest.ts new file mode 100644 index 000000000..f5d4a298f --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerRequest.ts @@ -0,0 +1,17 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetServerRequest { + /** + * server_id is the identifier of the specific server to get. + */ + 'server_id'?: (number | string | Long); +} + +export interface GetServerRequest__Output { + /** + * server_id is the identifier of the specific server to get. + */ + 'server_id': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerResponse.ts new file mode 100644 index 000000000..fe0078209 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerResponse.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Server as _grpc_channelz_v1_Server, Server__Output as _grpc_channelz_v1_Server__Output } from '../../../grpc/channelz/v1/Server'; + +export interface GetServerResponse { + /** + * The Server that corresponds to the requested server_id. This field + * should be set. + */ + 'server'?: (_grpc_channelz_v1_Server | null); +} + +export interface GetServerResponse__Output { + /** + * The Server that corresponds to the requested server_id. This field + * should be set. + */ + 'server': (_grpc_channelz_v1_Server__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsRequest.ts new file mode 100644 index 000000000..c33056edc --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsRequest.ts @@ -0,0 +1,39 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetServerSocketsRequest { + 'server_id'?: (number | string | Long); + /** + * start_socket_id indicates that only sockets at or above this id should be + * included in the results. + * To request the first page, this must be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_socket_id'?: (number | string | Long); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results'?: (number | string | Long); +} + +export interface GetServerSocketsRequest__Output { + 'server_id': (string); + /** + * start_socket_id indicates that only sockets at or above this id should be + * included in the results. + * To request the first page, this must be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_socket_id': (string); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsResponse.ts new file mode 100644 index 000000000..112f277e3 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsResponse.ts @@ -0,0 +1,33 @@ +// Original file: proto/channelz.proto + +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; + +export interface GetServerSocketsResponse { + /** + * list of socket refs that the connection detail service knows about. Sorted in + * ascending socket_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'socket_ref'?: (_grpc_channelz_v1_SocketRef)[]; + /** + * If set, indicates that the list of sockets is the final list. Requesting + * more sockets will only return more if they are created after this RPC + * completes. + */ + 'end'?: (boolean); +} + +export interface GetServerSocketsResponse__Output { + /** + * list of socket refs that the connection detail service knows about. Sorted in + * ascending socket_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'socket_ref': (_grpc_channelz_v1_SocketRef__Output)[]; + /** + * If set, indicates that the list of sockets is the final list. Requesting + * more sockets will only return more if they are created after this RPC + * completes. + */ + 'end': (boolean); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersRequest.ts new file mode 100644 index 000000000..2defea62d --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersRequest.ts @@ -0,0 +1,37 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetServersRequest { + /** + * start_server_id indicates that only servers at or above this id should be + * included in the results. + * To request the first page, this must be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_server_id'?: (number | string | Long); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results'?: (number | string | Long); +} + +export interface GetServersRequest__Output { + /** + * start_server_id indicates that only servers at or above this id should be + * included in the results. + * To request the first page, this must be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_server_id': (string); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersResponse.ts new file mode 100644 index 000000000..b07893b8c --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersResponse.ts @@ -0,0 +1,33 @@ +// Original file: proto/channelz.proto + +import type { Server as _grpc_channelz_v1_Server, Server__Output as _grpc_channelz_v1_Server__Output } from '../../../grpc/channelz/v1/Server'; + +export interface GetServersResponse { + /** + * list of servers that the connection detail service knows about. Sorted in + * ascending server_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'server'?: (_grpc_channelz_v1_Server)[]; + /** + * If set, indicates that the list of servers is the final list. Requesting + * more servers will only return more if they are created after this RPC + * completes. + */ + 'end'?: (boolean); +} + +export interface GetServersResponse__Output { + /** + * list of servers that the connection detail service knows about. Sorted in + * ascending server_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'server': (_grpc_channelz_v1_Server__Output)[]; + /** + * If set, indicates that the list of servers is the final list. Requesting + * more servers will only return more if they are created after this RPC + * completes. + */ + 'end': (boolean); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketRequest.ts new file mode 100644 index 000000000..b3dc1608e --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketRequest.ts @@ -0,0 +1,29 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetSocketRequest { + /** + * socket_id is the identifier of the specific socket to get. + */ + 'socket_id'?: (number | string | Long); + /** + * If true, the response will contain only high level information + * that is inexpensive to obtain. Fields thay may be omitted are + * documented. + */ + 'summary'?: (boolean); +} + +export interface GetSocketRequest__Output { + /** + * socket_id is the identifier of the specific socket to get. + */ + 'socket_id': (string); + /** + * If true, the response will contain only high level information + * that is inexpensive to obtain. Fields thay may be omitted are + * documented. + */ + 'summary': (boolean); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketResponse.ts new file mode 100644 index 000000000..b6304b7f0 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketResponse.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Socket as _grpc_channelz_v1_Socket, Socket__Output as _grpc_channelz_v1_Socket__Output } from '../../../grpc/channelz/v1/Socket'; + +export interface GetSocketResponse { + /** + * The Socket that corresponds to the requested socket_id. This field + * should be set. + */ + 'socket'?: (_grpc_channelz_v1_Socket | null); +} + +export interface GetSocketResponse__Output { + /** + * The Socket that corresponds to the requested socket_id. This field + * should be set. + */ + 'socket': (_grpc_channelz_v1_Socket__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelRequest.ts new file mode 100644 index 000000000..f481a81d2 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelRequest.ts @@ -0,0 +1,17 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetSubchannelRequest { + /** + * subchannel_id is the identifier of the specific subchannel to get. + */ + 'subchannel_id'?: (number | string | Long); +} + +export interface GetSubchannelRequest__Output { + /** + * subchannel_id is the identifier of the specific subchannel to get. + */ + 'subchannel_id': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelResponse.ts new file mode 100644 index 000000000..57d2bf2dc --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelResponse.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Subchannel as _grpc_channelz_v1_Subchannel, Subchannel__Output as _grpc_channelz_v1_Subchannel__Output } from '../../../grpc/channelz/v1/Subchannel'; + +export interface GetSubchannelResponse { + /** + * The Subchannel that corresponds to the requested subchannel_id. This + * field should be set. + */ + 'subchannel'?: (_grpc_channelz_v1_Subchannel | null); +} + +export interface GetSubchannelResponse__Output { + /** + * The Subchannel that corresponds to the requested subchannel_id. This + * field should be set. + */ + 'subchannel': (_grpc_channelz_v1_Subchannel__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsRequest.ts new file mode 100644 index 000000000..a122d7a85 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsRequest.ts @@ -0,0 +1,37 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetTopChannelsRequest { + /** + * start_channel_id indicates that only channels at or above this id should be + * included in the results. + * To request the first page, this should be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_channel_id'?: (number | string | Long); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results'?: (number | string | Long); +} + +export interface GetTopChannelsRequest__Output { + /** + * start_channel_id indicates that only channels at or above this id should be + * included in the results. + * To request the first page, this should be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_channel_id': (string); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsResponse.ts new file mode 100644 index 000000000..d96e63673 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsResponse.ts @@ -0,0 +1,33 @@ +// Original file: proto/channelz.proto + +import type { Channel as _grpc_channelz_v1_Channel, Channel__Output as _grpc_channelz_v1_Channel__Output } from '../../../grpc/channelz/v1/Channel'; + +export interface GetTopChannelsResponse { + /** + * list of channels that the connection detail service knows about. Sorted in + * ascending channel_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'channel'?: (_grpc_channelz_v1_Channel)[]; + /** + * If set, indicates that the list of channels is the final list. Requesting + * more channels can only return more if they are created after this RPC + * completes. + */ + 'end'?: (boolean); +} + +export interface GetTopChannelsResponse__Output { + /** + * list of channels that the connection detail service knows about. Sorted in + * ascending channel_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'channel': (_grpc_channelz_v1_Channel__Output)[]; + /** + * If set, indicates that the list of channels is the final list. Requesting + * more channels can only return more if they are created after this RPC + * completes. + */ + 'end': (boolean); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Security.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Security.ts new file mode 100644 index 000000000..e555d698e --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Security.ts @@ -0,0 +1,87 @@ +// Original file: proto/channelz.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +export interface _grpc_channelz_v1_Security_OtherSecurity { + /** + * The human readable version of the value. + */ + 'name'?: (string); + /** + * The actual security details message. + */ + 'value'?: (_google_protobuf_Any | null); +} + +export interface _grpc_channelz_v1_Security_OtherSecurity__Output { + /** + * The human readable version of the value. + */ + 'name': (string); + /** + * The actual security details message. + */ + 'value': (_google_protobuf_Any__Output | null); +} + +export interface _grpc_channelz_v1_Security_Tls { + /** + * The cipher suite name in the RFC 4346 format: + * https://tools.ietf.org/html/rfc4346#appendix-C + */ + 'standard_name'?: (string); + /** + * Some other way to describe the cipher suite if + * the RFC 4346 name is not available. + */ + 'other_name'?: (string); + /** + * the certificate used by this endpoint. + */ + 'local_certificate'?: (Buffer | Uint8Array | string); + /** + * the certificate used by the remote endpoint. + */ + 'remote_certificate'?: (Buffer | Uint8Array | string); + 'cipher_suite'?: "standard_name"|"other_name"; +} + +export interface _grpc_channelz_v1_Security_Tls__Output { + /** + * The cipher suite name in the RFC 4346 format: + * https://tools.ietf.org/html/rfc4346#appendix-C + */ + 'standard_name'?: (string); + /** + * Some other way to describe the cipher suite if + * the RFC 4346 name is not available. + */ + 'other_name'?: (string); + /** + * the certificate used by this endpoint. + */ + 'local_certificate': (Buffer); + /** + * the certificate used by the remote endpoint. + */ + 'remote_certificate': (Buffer); + 'cipher_suite': "standard_name"|"other_name"; +} + +/** + * Security represents details about how secure the socket is. + */ +export interface Security { + 'tls'?: (_grpc_channelz_v1_Security_Tls | null); + 'other'?: (_grpc_channelz_v1_Security_OtherSecurity | null); + 'model'?: "tls"|"other"; +} + +/** + * Security represents details about how secure the socket is. + */ +export interface Security__Output { + 'tls'?: (_grpc_channelz_v1_Security_Tls__Output | null); + 'other'?: (_grpc_channelz_v1_Security_OtherSecurity__Output | null); + 'model': "tls"|"other"; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Server.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Server.ts new file mode 100644 index 000000000..958343358 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Server.ts @@ -0,0 +1,45 @@ +// Original file: proto/channelz.proto + +import type { ServerRef as _grpc_channelz_v1_ServerRef, ServerRef__Output as _grpc_channelz_v1_ServerRef__Output } from '../../../grpc/channelz/v1/ServerRef'; +import type { ServerData as _grpc_channelz_v1_ServerData, ServerData__Output as _grpc_channelz_v1_ServerData__Output } from '../../../grpc/channelz/v1/ServerData'; +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; + +/** + * Server represents a single server. There may be multiple servers in a single + * program. + */ +export interface Server { + /** + * The identifier for a Server. This should be set. + */ + 'ref'?: (_grpc_channelz_v1_ServerRef | null); + /** + * The associated data of the Server. + */ + 'data'?: (_grpc_channelz_v1_ServerData | null); + /** + * The sockets that the server is listening on. There are no ordering + * guarantees. This may be absent. + */ + 'listen_socket'?: (_grpc_channelz_v1_SocketRef)[]; +} + +/** + * Server represents a single server. There may be multiple servers in a single + * program. + */ +export interface Server__Output { + /** + * The identifier for a Server. This should be set. + */ + 'ref': (_grpc_channelz_v1_ServerRef__Output | null); + /** + * The associated data of the Server. + */ + 'data': (_grpc_channelz_v1_ServerData__Output | null); + /** + * The sockets that the server is listening on. There are no ordering + * guarantees. This may be absent. + */ + 'listen_socket': (_grpc_channelz_v1_SocketRef__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ServerData.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ServerData.ts new file mode 100644 index 000000000..ce48e36f5 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ServerData.ts @@ -0,0 +1,57 @@ +// Original file: proto/channelz.proto + +import type { ChannelTrace as _grpc_channelz_v1_ChannelTrace, ChannelTrace__Output as _grpc_channelz_v1_ChannelTrace__Output } from '../../../grpc/channelz/v1/ChannelTrace'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Long } from '@grpc/proto-loader'; + +/** + * ServerData is data for a specific Server. + */ +export interface ServerData { + /** + * A trace of recent events on the server. May be absent. + */ + 'trace'?: (_grpc_channelz_v1_ChannelTrace | null); + /** + * The number of incoming calls started on the server + */ + 'calls_started'?: (number | string | Long); + /** + * The number of incoming calls that have completed with an OK status + */ + 'calls_succeeded'?: (number | string | Long); + /** + * The number of incoming calls that have a completed with a non-OK status + */ + 'calls_failed'?: (number | string | Long); + /** + * The last time a call was started on the server. + */ + 'last_call_started_timestamp'?: (_google_protobuf_Timestamp | null); +} + +/** + * ServerData is data for a specific Server. + */ +export interface ServerData__Output { + /** + * A trace of recent events on the server. May be absent. + */ + 'trace': (_grpc_channelz_v1_ChannelTrace__Output | null); + /** + * The number of incoming calls started on the server + */ + 'calls_started': (string); + /** + * The number of incoming calls that have completed with an OK status + */ + 'calls_succeeded': (string); + /** + * The number of incoming calls that have a completed with a non-OK status + */ + 'calls_failed': (string); + /** + * The last time a call was started on the server. + */ + 'last_call_started_timestamp': (_google_protobuf_Timestamp__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ServerRef.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ServerRef.ts new file mode 100644 index 000000000..389183bdc --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ServerRef.ts @@ -0,0 +1,31 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * ServerRef is a reference to a Server. + */ +export interface ServerRef { + /** + * A globally unique identifier for this server. Must be a positive number. + */ + 'server_id'?: (number | string | Long); + /** + * An optional name associated with the server. + */ + 'name'?: (string); +} + +/** + * ServerRef is a reference to a Server. + */ +export interface ServerRef__Output { + /** + * A globally unique identifier for this server. Must be a positive number. + */ + 'server_id': (string); + /** + * An optional name associated with the server. + */ + 'name': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Socket.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Socket.ts new file mode 100644 index 000000000..5829afe98 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Socket.ts @@ -0,0 +1,70 @@ +// Original file: proto/channelz.proto + +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; +import type { SocketData as _grpc_channelz_v1_SocketData, SocketData__Output as _grpc_channelz_v1_SocketData__Output } from '../../../grpc/channelz/v1/SocketData'; +import type { Address as _grpc_channelz_v1_Address, Address__Output as _grpc_channelz_v1_Address__Output } from '../../../grpc/channelz/v1/Address'; +import type { Security as _grpc_channelz_v1_Security, Security__Output as _grpc_channelz_v1_Security__Output } from '../../../grpc/channelz/v1/Security'; + +/** + * Information about an actual connection. Pronounced "sock-ay". + */ +export interface Socket { + /** + * The identifier for the Socket. + */ + 'ref'?: (_grpc_channelz_v1_SocketRef | null); + /** + * Data specific to this Socket. + */ + 'data'?: (_grpc_channelz_v1_SocketData | null); + /** + * The locally bound address. + */ + 'local'?: (_grpc_channelz_v1_Address | null); + /** + * The remote bound address. May be absent. + */ + 'remote'?: (_grpc_channelz_v1_Address | null); + /** + * Security details for this socket. May be absent if not available, or + * there is no security on the socket. + */ + 'security'?: (_grpc_channelz_v1_Security | null); + /** + * Optional, represents the name of the remote endpoint, if different than + * the original target name. + */ + 'remote_name'?: (string); +} + +/** + * Information about an actual connection. Pronounced "sock-ay". + */ +export interface Socket__Output { + /** + * The identifier for the Socket. + */ + 'ref': (_grpc_channelz_v1_SocketRef__Output | null); + /** + * Data specific to this Socket. + */ + 'data': (_grpc_channelz_v1_SocketData__Output | null); + /** + * The locally bound address. + */ + 'local': (_grpc_channelz_v1_Address__Output | null); + /** + * The remote bound address. May be absent. + */ + 'remote': (_grpc_channelz_v1_Address__Output | null); + /** + * Security details for this socket. May be absent if not available, or + * there is no security on the socket. + */ + 'security': (_grpc_channelz_v1_Security__Output | null); + /** + * Optional, represents the name of the remote endpoint, if different than + * the original target name. + */ + 'remote_name': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketData.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketData.ts new file mode 100644 index 000000000..c62d4d10c --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketData.ts @@ -0,0 +1,150 @@ +// Original file: proto/channelz.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Int64Value as _google_protobuf_Int64Value, Int64Value__Output as _google_protobuf_Int64Value__Output } from '../../../google/protobuf/Int64Value'; +import type { SocketOption as _grpc_channelz_v1_SocketOption, SocketOption__Output as _grpc_channelz_v1_SocketOption__Output } from '../../../grpc/channelz/v1/SocketOption'; +import type { Long } from '@grpc/proto-loader'; + +/** + * SocketData is data associated for a specific Socket. The fields present + * are specific to the implementation, so there may be minor differences in + * the semantics. (e.g. flow control windows) + */ +export interface SocketData { + /** + * The number of streams that have been started. + */ + 'streams_started'?: (number | string | Long); + /** + * The number of streams that have ended successfully: + * On client side, received frame with eos bit set; + * On server side, sent frame with eos bit set. + */ + 'streams_succeeded'?: (number | string | Long); + /** + * The number of streams that have ended unsuccessfully: + * On client side, ended without receiving frame with eos bit set; + * On server side, ended without sending frame with eos bit set. + */ + 'streams_failed'?: (number | string | Long); + /** + * The number of grpc messages successfully sent on this socket. + */ + 'messages_sent'?: (number | string | Long); + /** + * The number of grpc messages received on this socket. + */ + 'messages_received'?: (number | string | Long); + /** + * The number of keep alives sent. This is typically implemented with HTTP/2 + * ping messages. + */ + 'keep_alives_sent'?: (number | string | Long); + /** + * The last time a stream was created by this endpoint. Usually unset for + * servers. + */ + 'last_local_stream_created_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * The last time a stream was created by the remote endpoint. Usually unset + * for clients. + */ + 'last_remote_stream_created_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * The last time a message was sent by this endpoint. + */ + 'last_message_sent_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * The last time a message was received by this endpoint. + */ + 'last_message_received_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * The amount of window, granted to the local endpoint by the remote endpoint. + * This may be slightly out of date due to network latency. This does NOT + * include stream level or TCP level flow control info. + */ + 'local_flow_control_window'?: (_google_protobuf_Int64Value | null); + /** + * The amount of window, granted to the remote endpoint by the local endpoint. + * This may be slightly out of date due to network latency. This does NOT + * include stream level or TCP level flow control info. + */ + 'remote_flow_control_window'?: (_google_protobuf_Int64Value | null); + /** + * Socket options set on this socket. May be absent if 'summary' is set + * on GetSocketRequest. + */ + 'option'?: (_grpc_channelz_v1_SocketOption)[]; +} + +/** + * SocketData is data associated for a specific Socket. The fields present + * are specific to the implementation, so there may be minor differences in + * the semantics. (e.g. flow control windows) + */ +export interface SocketData__Output { + /** + * The number of streams that have been started. + */ + 'streams_started': (string); + /** + * The number of streams that have ended successfully: + * On client side, received frame with eos bit set; + * On server side, sent frame with eos bit set. + */ + 'streams_succeeded': (string); + /** + * The number of streams that have ended unsuccessfully: + * On client side, ended without receiving frame with eos bit set; + * On server side, ended without sending frame with eos bit set. + */ + 'streams_failed': (string); + /** + * The number of grpc messages successfully sent on this socket. + */ + 'messages_sent': (string); + /** + * The number of grpc messages received on this socket. + */ + 'messages_received': (string); + /** + * The number of keep alives sent. This is typically implemented with HTTP/2 + * ping messages. + */ + 'keep_alives_sent': (string); + /** + * The last time a stream was created by this endpoint. Usually unset for + * servers. + */ + 'last_local_stream_created_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * The last time a stream was created by the remote endpoint. Usually unset + * for clients. + */ + 'last_remote_stream_created_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * The last time a message was sent by this endpoint. + */ + 'last_message_sent_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * The last time a message was received by this endpoint. + */ + 'last_message_received_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * The amount of window, granted to the local endpoint by the remote endpoint. + * This may be slightly out of date due to network latency. This does NOT + * include stream level or TCP level flow control info. + */ + 'local_flow_control_window': (_google_protobuf_Int64Value__Output | null); + /** + * The amount of window, granted to the remote endpoint by the local endpoint. + * This may be slightly out of date due to network latency. This does NOT + * include stream level or TCP level flow control info. + */ + 'remote_flow_control_window': (_google_protobuf_Int64Value__Output | null); + /** + * Socket options set on this socket. May be absent if 'summary' is set + * on GetSocketRequest. + */ + 'option': (_grpc_channelz_v1_SocketOption__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOption.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOption.ts new file mode 100644 index 000000000..115b36aae --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOption.ts @@ -0,0 +1,47 @@ +// Original file: proto/channelz.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * SocketOption represents socket options for a socket. Specifically, these + * are the options returned by getsockopt(). + */ +export interface SocketOption { + /** + * The full name of the socket option. Typically this will be the upper case + * name, such as "SO_REUSEPORT". + */ + 'name'?: (string); + /** + * The human readable value of this socket option. At least one of value or + * additional will be set. + */ + 'value'?: (string); + /** + * Additional data associated with the socket option. At least one of value + * or additional will be set. + */ + 'additional'?: (_google_protobuf_Any | null); +} + +/** + * SocketOption represents socket options for a socket. Specifically, these + * are the options returned by getsockopt(). + */ +export interface SocketOption__Output { + /** + * The full name of the socket option. Typically this will be the upper case + * name, such as "SO_REUSEPORT". + */ + 'name': (string); + /** + * The human readable value of this socket option. At least one of value or + * additional will be set. + */ + 'value': (string); + /** + * Additional data associated with the socket option. At least one of value + * or additional will be set. + */ + 'additional': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionLinger.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionLinger.ts new file mode 100644 index 000000000..d83fa3238 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionLinger.ts @@ -0,0 +1,33 @@ +// Original file: proto/channelz.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; + +/** + * For use with SocketOption's additional field. This is primarily used for + * SO_LINGER. + */ +export interface SocketOptionLinger { + /** + * active maps to `struct linger.l_onoff` + */ + 'active'?: (boolean); + /** + * duration maps to `struct linger.l_linger` + */ + 'duration'?: (_google_protobuf_Duration | null); +} + +/** + * For use with SocketOption's additional field. This is primarily used for + * SO_LINGER. + */ +export interface SocketOptionLinger__Output { + /** + * active maps to `struct linger.l_onoff` + */ + 'active': (boolean); + /** + * duration maps to `struct linger.l_linger` + */ + 'duration': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTcpInfo.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTcpInfo.ts new file mode 100644 index 000000000..2f8affe80 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTcpInfo.ts @@ -0,0 +1,74 @@ +// Original file: proto/channelz.proto + + +/** + * For use with SocketOption's additional field. Tcp info for + * SOL_TCP and TCP_INFO. + */ +export interface SocketOptionTcpInfo { + 'tcpi_state'?: (number); + 'tcpi_ca_state'?: (number); + 'tcpi_retransmits'?: (number); + 'tcpi_probes'?: (number); + 'tcpi_backoff'?: (number); + 'tcpi_options'?: (number); + 'tcpi_snd_wscale'?: (number); + 'tcpi_rcv_wscale'?: (number); + 'tcpi_rto'?: (number); + 'tcpi_ato'?: (number); + 'tcpi_snd_mss'?: (number); + 'tcpi_rcv_mss'?: (number); + 'tcpi_unacked'?: (number); + 'tcpi_sacked'?: (number); + 'tcpi_lost'?: (number); + 'tcpi_retrans'?: (number); + 'tcpi_fackets'?: (number); + 'tcpi_last_data_sent'?: (number); + 'tcpi_last_ack_sent'?: (number); + 'tcpi_last_data_recv'?: (number); + 'tcpi_last_ack_recv'?: (number); + 'tcpi_pmtu'?: (number); + 'tcpi_rcv_ssthresh'?: (number); + 'tcpi_rtt'?: (number); + 'tcpi_rttvar'?: (number); + 'tcpi_snd_ssthresh'?: (number); + 'tcpi_snd_cwnd'?: (number); + 'tcpi_advmss'?: (number); + 'tcpi_reordering'?: (number); +} + +/** + * For use with SocketOption's additional field. Tcp info for + * SOL_TCP and TCP_INFO. + */ +export interface SocketOptionTcpInfo__Output { + 'tcpi_state': (number); + 'tcpi_ca_state': (number); + 'tcpi_retransmits': (number); + 'tcpi_probes': (number); + 'tcpi_backoff': (number); + 'tcpi_options': (number); + 'tcpi_snd_wscale': (number); + 'tcpi_rcv_wscale': (number); + 'tcpi_rto': (number); + 'tcpi_ato': (number); + 'tcpi_snd_mss': (number); + 'tcpi_rcv_mss': (number); + 'tcpi_unacked': (number); + 'tcpi_sacked': (number); + 'tcpi_lost': (number); + 'tcpi_retrans': (number); + 'tcpi_fackets': (number); + 'tcpi_last_data_sent': (number); + 'tcpi_last_ack_sent': (number); + 'tcpi_last_data_recv': (number); + 'tcpi_last_ack_recv': (number); + 'tcpi_pmtu': (number); + 'tcpi_rcv_ssthresh': (number); + 'tcpi_rtt': (number); + 'tcpi_rttvar': (number); + 'tcpi_snd_ssthresh': (number); + 'tcpi_snd_cwnd': (number); + 'tcpi_advmss': (number); + 'tcpi_reordering': (number); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTimeout.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTimeout.ts new file mode 100644 index 000000000..185839b2c --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTimeout.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; + +/** + * For use with SocketOption's additional field. This is primarily used for + * SO_RCVTIMEO and SO_SNDTIMEO + */ +export interface SocketOptionTimeout { + 'duration'?: (_google_protobuf_Duration | null); +} + +/** + * For use with SocketOption's additional field. This is primarily used for + * SO_RCVTIMEO and SO_SNDTIMEO + */ +export interface SocketOptionTimeout__Output { + 'duration': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketRef.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketRef.ts new file mode 100644 index 000000000..52fdb2bd3 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketRef.ts @@ -0,0 +1,31 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * SocketRef is a reference to a Socket. + */ +export interface SocketRef { + /** + * The globally unique id for this socket. Must be a positive number. + */ + 'socket_id'?: (number | string | Long); + /** + * An optional name associated with the socket. + */ + 'name'?: (string); +} + +/** + * SocketRef is a reference to a Socket. + */ +export interface SocketRef__Output { + /** + * The globally unique id for this socket. Must be a positive number. + */ + 'socket_id': (string); + /** + * An optional name associated with the socket. + */ + 'name': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Subchannel.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Subchannel.ts new file mode 100644 index 000000000..7122fac83 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Subchannel.ts @@ -0,0 +1,70 @@ +// Original file: proto/channelz.proto + +import type { SubchannelRef as _grpc_channelz_v1_SubchannelRef, SubchannelRef__Output as _grpc_channelz_v1_SubchannelRef__Output } from '../../../grpc/channelz/v1/SubchannelRef'; +import type { ChannelData as _grpc_channelz_v1_ChannelData, ChannelData__Output as _grpc_channelz_v1_ChannelData__Output } from '../../../grpc/channelz/v1/ChannelData'; +import type { ChannelRef as _grpc_channelz_v1_ChannelRef, ChannelRef__Output as _grpc_channelz_v1_ChannelRef__Output } from '../../../grpc/channelz/v1/ChannelRef'; +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; + +/** + * Subchannel is a logical grouping of channels, subchannels, and sockets. + * A subchannel is load balanced over by it's ancestor + */ +export interface Subchannel { + /** + * The identifier for this channel. + */ + 'ref'?: (_grpc_channelz_v1_SubchannelRef | null); + /** + * Data specific to this channel. + */ + 'data'?: (_grpc_channelz_v1_ChannelData | null); + /** + * There are no ordering guarantees on the order of channel refs. + * There may not be cycles in the ref graph. + * A channel ref may be present in more than one channel or subchannel. + */ + 'channel_ref'?: (_grpc_channelz_v1_ChannelRef)[]; + /** + * At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + * There are no ordering guarantees on the order of subchannel refs. + * There may not be cycles in the ref graph. + * A sub channel ref may be present in more than one channel or subchannel. + */ + 'subchannel_ref'?: (_grpc_channelz_v1_SubchannelRef)[]; + /** + * There are no ordering guarantees on the order of sockets. + */ + 'socket_ref'?: (_grpc_channelz_v1_SocketRef)[]; +} + +/** + * Subchannel is a logical grouping of channels, subchannels, and sockets. + * A subchannel is load balanced over by it's ancestor + */ +export interface Subchannel__Output { + /** + * The identifier for this channel. + */ + 'ref': (_grpc_channelz_v1_SubchannelRef__Output | null); + /** + * Data specific to this channel. + */ + 'data': (_grpc_channelz_v1_ChannelData__Output | null); + /** + * There are no ordering guarantees on the order of channel refs. + * There may not be cycles in the ref graph. + * A channel ref may be present in more than one channel or subchannel. + */ + 'channel_ref': (_grpc_channelz_v1_ChannelRef__Output)[]; + /** + * At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + * There are no ordering guarantees on the order of subchannel refs. + * There may not be cycles in the ref graph. + * A sub channel ref may be present in more than one channel or subchannel. + */ + 'subchannel_ref': (_grpc_channelz_v1_SubchannelRef__Output)[]; + /** + * There are no ordering guarantees on the order of sockets. + */ + 'socket_ref': (_grpc_channelz_v1_SocketRef__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SubchannelRef.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SubchannelRef.ts new file mode 100644 index 000000000..b6911c773 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SubchannelRef.ts @@ -0,0 +1,31 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * SubchannelRef is a reference to a Subchannel. + */ +export interface SubchannelRef { + /** + * The globally unique id for this subchannel. Must be a positive number. + */ + 'subchannel_id'?: (number | string | Long); + /** + * An optional name associated with the subchannel. + */ + 'name'?: (string); +} + +/** + * SubchannelRef is a reference to a Subchannel. + */ +export interface SubchannelRef__Output { + /** + * The globally unique id for this subchannel. Must be a positive number. + */ + 'subchannel_id': (string); + /** + * An optional name associated with the subchannel. + */ + 'name': (string); +} diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts new file mode 100644 index 000000000..6faa1976d --- /dev/null +++ b/packages/grpc-js/src/http_proxy.ts @@ -0,0 +1,301 @@ +/* + * Copyright 2019 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. + * + */ + +import { log } from './logging'; +import { LogVerbosity } from './constants'; +import { getDefaultAuthority } from './resolver'; +import { Socket } from 'net'; +import * as http from 'http'; +import * as tls from 'tls'; +import * as logging from './logging'; +import { + SubchannelAddress, + isTcpSubchannelAddress, + subchannelAddressToString, +} from './subchannel-address'; +import { ChannelOptions } from './channel-options'; +import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; +import { URL } from 'url'; + +const TRACER_NAME = 'proxy'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +interface ProxyInfo { + address?: string; + creds?: string; +} + +function getProxyInfo(): ProxyInfo { + let proxyEnv = ''; + let envVar = ''; + /* Prefer using 'grpc_proxy'. Fallback on 'http_proxy' if it is not set. + * Also prefer using 'https_proxy' with fallback on 'http_proxy'. The + * fallback behavior can be removed if there's a demand for it. + */ + if (process.env.grpc_proxy) { + envVar = 'grpc_proxy'; + proxyEnv = process.env.grpc_proxy; + } else if (process.env.https_proxy) { + envVar = 'https_proxy'; + proxyEnv = process.env.https_proxy; + } else if (process.env.http_proxy) { + envVar = 'http_proxy'; + proxyEnv = process.env.http_proxy; + } else { + return {}; + } + let proxyUrl: URL; + try { + proxyUrl = new URL(proxyEnv); + } catch (e) { + log(LogVerbosity.ERROR, `cannot parse value of "${envVar}" env var`); + return {}; + } + if (proxyUrl.protocol !== 'http:') { + log( + LogVerbosity.ERROR, + `"${proxyUrl.protocol}" scheme not supported in proxy URI` + ); + return {}; + } + let userCred: string | null = null; + if (proxyUrl.username) { + if (proxyUrl.password) { + log(LogVerbosity.INFO, 'userinfo found in proxy URI'); + userCred = `${proxyUrl.username}:${proxyUrl.password}`; + } else { + userCred = proxyUrl.username; + } + } + const hostname = proxyUrl.hostname; + let port = proxyUrl.port; + /* The proxy URL uses the scheme "http:", which has a default port number of + * 80. We need to set that explicitly here if it is omitted because otherwise + * it will use gRPC's default port 443. */ + if (port === '') { + port = '80'; + } + const result: ProxyInfo = { + address: `${hostname}:${port}`, + }; + if (userCred) { + result.creds = userCred; + } + trace( + 'Proxy server ' + result.address + ' set by environment variable ' + envVar + ); + return result; +} + +function getNoProxyHostList(): string[] { + /* Prefer using 'no_grpc_proxy'. Fallback on 'no_proxy' if it is not set. */ + let noProxyStr: string | undefined = process.env.no_grpc_proxy; + let envVar = 'no_grpc_proxy'; + if (!noProxyStr) { + noProxyStr = process.env.no_proxy; + envVar = 'no_proxy'; + } + if (noProxyStr) { + trace('No proxy server list set by environment variable ' + envVar); + return noProxyStr.split(','); + } else { + return []; + } +} + +export interface ProxyMapResult { + target: GrpcUri; + extraOptions: ChannelOptions; +} + +export function mapProxyName( + target: GrpcUri, + options: ChannelOptions +): ProxyMapResult { + const noProxyResult: ProxyMapResult = { + target: target, + extraOptions: {}, + }; + if ((options['grpc.enable_http_proxy'] ?? 1) === 0) { + return noProxyResult; + } + if (target.scheme === 'unix') { + return noProxyResult; + } + const proxyInfo = getProxyInfo(); + if (!proxyInfo.address) { + return noProxyResult; + } + const hostPort = splitHostPort(target.path); + if (!hostPort) { + return noProxyResult; + } + const serverHost = hostPort.host; + for (const host of getNoProxyHostList()) { + if (host === serverHost) { + trace( + 'Not using proxy for target in no_proxy list: ' + uriToString(target) + ); + return noProxyResult; + } + } + const extraOptions: ChannelOptions = { + 'grpc.http_connect_target': uriToString(target), + }; + if (proxyInfo.creds) { + extraOptions['grpc.http_connect_creds'] = proxyInfo.creds; + } + return { + target: { + scheme: 'dns', + path: proxyInfo.address, + }, + extraOptions: extraOptions, + }; +} + +export interface ProxyConnectionResult { + socket?: Socket; + realTarget?: GrpcUri; +} + +export function getProxiedConnection( + address: SubchannelAddress, + channelOptions: ChannelOptions, + connectionOptions: tls.ConnectionOptions +): Promise { + if (!('grpc.http_connect_target' in channelOptions)) { + return Promise.resolve({}); + } + const realTarget = channelOptions['grpc.http_connect_target'] as string; + const parsedTarget = parseUri(realTarget); + if (parsedTarget === null) { + return Promise.resolve({}); + } + const options: http.RequestOptions = { + method: 'CONNECT', + path: parsedTarget.path, + }; + const headers: http.OutgoingHttpHeaders = { + Host: parsedTarget.path, + }; + // Connect to the subchannel address as a proxy + if (isTcpSubchannelAddress(address)) { + options.host = address.host; + options.port = address.port; + } else { + options.socketPath = address.path; + } + if ('grpc.http_connect_creds' in channelOptions) { + headers['Proxy-Authorization'] = + 'Basic ' + + Buffer.from( + channelOptions['grpc.http_connect_creds'] as string + ).toString('base64'); + } + options.headers = headers + const proxyAddressString = subchannelAddressToString(address); + trace('Using proxy ' + proxyAddressString + ' to connect to ' + options.path); + return new Promise((resolve, reject) => { + const request = http.request(options); + request.once('connect', (res, socket, head) => { + request.removeAllListeners(); + socket.removeAllListeners(); + if (res.statusCode === 200) { + trace( + 'Successfully connected to ' + + options.path + + ' through proxy ' + + proxyAddressString + ); + if ('secureContext' in connectionOptions) { + /* The proxy is connecting to a TLS server, so upgrade this socket + * connection to a TLS connection. + * This is a workaround for https://github.com/nodejs/node/issues/32922 + * See https://github.com/grpc/grpc-node/pull/1369 for more info. */ + const targetPath = getDefaultAuthority(parsedTarget); + const hostPort = splitHostPort(targetPath); + const remoteHost = hostPort?.host ?? targetPath; + + const cts = tls.connect( + { + host: remoteHost, + servername: remoteHost, + socket: socket, + ...connectionOptions, + }, + () => { + trace( + 'Successfully established a TLS connection to ' + + options.path + + ' through proxy ' + + proxyAddressString + ); + resolve({ socket: cts, realTarget: parsedTarget }); + } + ); + cts.on('error', (error: Error) => { + trace('Failed to establish a TLS connection to ' + + options.path + + ' through proxy ' + + proxyAddressString + + ' with error ' + + error.message); + reject(); + }); + } else { + trace( + 'Successfully established a plaintext connection to ' + + options.path + + ' through proxy ' + + proxyAddressString + ); + resolve({ + socket, + realTarget: parsedTarget, + }); + } + } else { + log( + LogVerbosity.ERROR, + 'Failed to connect to ' + + options.path + + ' through proxy ' + + proxyAddressString + + ' with status ' + + res.statusCode + ); + reject(); + } + }); + request.once('error', (err) => { + request.removeAllListeners(); + log( + LogVerbosity.ERROR, + 'Failed to connect to proxy ' + + proxyAddressString + + ' with error ' + + err.message + ); + reject(); + }); + request.end(); + }); +} diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts new file mode 100644 index 000000000..70178a89e --- /dev/null +++ b/packages/grpc-js/src/index.ts @@ -0,0 +1,290 @@ +/* + * Copyright 2019 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. + * + */ + +import { + ClientDuplexStream, + ClientReadableStream, + ClientUnaryCall, + ClientWritableStream, + ServiceError, +} from './call'; +import { CallCredentials, OAuth2Client } from './call-credentials'; +import { StatusObject } from './call-interface'; +import { Channel, ChannelImplementation } from './channel'; +import { CompressionAlgorithms } from './compression-algorithms'; +import { ConnectivityState } from './connectivity-state'; +import { ChannelCredentials } from './channel-credentials'; +import { + CallOptions, + Client, + ClientOptions, + CallInvocationTransformer, + CallProperties, + UnaryCallback, +} from './client'; +import { LogVerbosity, Status, Propagate } from './constants'; +import * as logging from './logging'; +import { + Deserialize, + loadPackageDefinition, + makeClientConstructor, + MethodDefinition, + Serialize, + ServiceDefinition, +} from './make-client'; +import { Metadata, MetadataOptions, MetadataValue } from './metadata'; +import { + Server, + UntypedHandleCall, + UntypedServiceImplementation, +} from './server'; +import { KeyCertPair, ServerCredentials } from './server-credentials'; +import { StatusBuilder } from './status-builder'; +import { + handleBidiStreamingCall, + handleServerStreamingCall, + handleClientStreamingCall, + handleUnaryCall, + sendUnaryData, + ServerUnaryCall, + ServerReadableStream, + ServerWritableStream, + ServerDuplexStream, + ServerErrorResponse, +} from './server-call'; + +export { OAuth2Client }; + +/**** Client Credentials ****/ + +// Using assign only copies enumerable properties, which is what we want +export const credentials = { + /** + * Combine a ChannelCredentials with any number of CallCredentials into a + * single ChannelCredentials object. + * @param channelCredentials The ChannelCredentials object. + * @param callCredentials Any number of CallCredentials objects. + * @return The resulting ChannelCredentials object. + */ + combineChannelCredentials: ( + channelCredentials: ChannelCredentials, + ...callCredentials: CallCredentials[] + ): ChannelCredentials => { + return callCredentials.reduce( + (acc, other) => acc.compose(other), + channelCredentials + ); + }, + + /** + * Combine any number of CallCredentials into a single CallCredentials + * object. + * @param first The first CallCredentials object. + * @param additional Any number of additional CallCredentials objects. + * @return The resulting CallCredentials object. + */ + combineCallCredentials: ( + first: CallCredentials, + ...additional: CallCredentials[] + ): CallCredentials => { + return additional.reduce((acc, other) => acc.compose(other), first); + }, + + // from channel-credentials.ts + createInsecure: ChannelCredentials.createInsecure, + createSsl: ChannelCredentials.createSsl, + createFromSecureContext: ChannelCredentials.createFromSecureContext, + + // from call-credentials.ts + createFromMetadataGenerator: CallCredentials.createFromMetadataGenerator, + createFromGoogleCredential: CallCredentials.createFromGoogleCredential, + createEmpty: CallCredentials.createEmpty, +}; + +/**** Metadata ****/ + +export { Metadata, MetadataOptions, MetadataValue }; + +/**** Constants ****/ + +export { + LogVerbosity as logVerbosity, + Status as status, + ConnectivityState as connectivityState, + Propagate as propagate, + CompressionAlgorithms as compressionAlgorithms + // TODO: Other constants as well +}; + +/**** Client ****/ + +export { + Client, + ClientOptions, + loadPackageDefinition, + makeClientConstructor, + makeClientConstructor as makeGenericClientConstructor, + CallProperties, + CallInvocationTransformer, + ChannelImplementation as Channel, + Channel as ChannelInterface, + UnaryCallback as requestCallback, +}; + +/** + * Close a Client object. + * @param client The client to close. + */ +export const closeClient = (client: Client) => client.close(); + +export const waitForClientReady = ( + client: Client, + deadline: Date | number, + callback: (error?: Error) => void +) => client.waitForReady(deadline, callback); + +/* Interfaces */ + +export { + sendUnaryData, + ChannelCredentials, + CallCredentials, + Deadline, + Serialize as serialize, + Deserialize as deserialize, + ClientUnaryCall, + ClientReadableStream, + ClientWritableStream, + ClientDuplexStream, + CallOptions, + MethodDefinition, + StatusObject, + ServiceError, + ServerUnaryCall, + ServerReadableStream, + ServerWritableStream, + ServerDuplexStream, + ServerErrorResponse, + ServiceDefinition, + UntypedHandleCall, + UntypedServiceImplementation, +}; + +/**** Server ****/ + +export { + handleBidiStreamingCall, + handleServerStreamingCall, + handleUnaryCall, + handleClientStreamingCall, +}; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +export type Call = + | ClientUnaryCall + | ClientReadableStream + | ClientWritableStream + | ClientDuplexStream; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +/**** Unimplemented function stubs ****/ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export const loadObject = (value: any, options: any): never => { + throw new Error( + 'Not available in this library. Use @grpc/proto-loader and loadPackageDefinition instead' + ); +}; + +export const load = (filename: any, format: any, options: any): never => { + throw new Error( + 'Not available in this library. Use @grpc/proto-loader and loadPackageDefinition instead' + ); +}; + +export const setLogger = (logger: Partial): void => { + logging.setLogger(logger); +}; + +export const setLogVerbosity = (verbosity: LogVerbosity): void => { + logging.setLoggerVerbosity(verbosity); +}; + +export { Server }; +export { ServerCredentials }; +export { KeyCertPair }; + +export const getClientChannel = (client: Client) => { + return Client.prototype.getChannel.call(client); +}; + +export { StatusBuilder }; + +export { Listener, InterceptingListener } from './call-interface'; + +export { + Requester, + ListenerBuilder, + RequesterBuilder, + Interceptor, + InterceptorOptions, + InterceptorProvider, + InterceptingCall, + InterceptorConfigurationError, + NextCall +} from './client-interceptors'; + +export { + GrpcObject, + ServiceClientConstructor, + ProtobufTypeDefinition +} from './make-client'; + +export { ChannelOptions } from './channel-options'; + +export { + getChannelzServiceDefinition, + getChannelzHandlers +} from './channelz'; + +export { addAdminServicesToServer } from './admin'; + +import * as experimental from './experimental'; +export { experimental }; + +import * as resolver_dns from './resolver-dns'; +import * as resolver_uds from './resolver-uds'; +import * as resolver_ip from './resolver-ip'; +import * as load_balancer_pick_first from './load-balancer-pick-first'; +import * as load_balancer_round_robin from './load-balancer-round-robin'; +import * as load_balancer_outlier_detection from './load-balancer-outlier-detection'; +import * as channelz from './channelz'; +import { Deadline } from './deadline'; + +const clientVersion = require('../../package.json').version; + +(() => { + logging.trace(LogVerbosity.DEBUG, 'index', 'Loading @grpc/grpc-js version ' + clientVersion); + resolver_dns.setup(); + resolver_uds.setup(); + resolver_ip.setup(); + load_balancer_pick_first.setup(); + load_balancer_round_robin.setup(); + load_balancer_outlier_detection.setup(); + channelz.setup(); +})(); diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts new file mode 100644 index 000000000..242a1cbd8 --- /dev/null +++ b/packages/grpc-js/src/internal-channel.ts @@ -0,0 +1,625 @@ +/* + * Copyright 2019 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. + * + */ + +import { ChannelCredentials } from './channel-credentials'; +import { ChannelOptions } from './channel-options'; +import { ResolvingLoadBalancer } from './resolving-load-balancer'; +import { SubchannelPool, getSubchannelPool } from './subchannel-pool'; +import { ChannelControlHelper } from './load-balancer'; +import { UnavailablePicker, Picker } from './picker'; +import { Metadata } from './metadata'; +import { Status, LogVerbosity, Propagate } from './constants'; +import { FilterStackFactory } from './filter-stack'; +import { CompressionFilterFactory } from './compression-filter'; +import { + CallConfig, + ConfigSelector, + getDefaultAuthority, + mapUriDefaultScheme, +} from './resolver'; +import { trace } from './logging'; +import { SubchannelAddress } from './subchannel-address'; +import { mapProxyName } from './http_proxy'; +import { GrpcUri, parseUri, uriToString } from './uri-parser'; +import { ServerSurfaceCall } from './server-call'; + +import { ConnectivityState } from './connectivity-state'; +import { ChannelInfo, ChannelRef, ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzChannel, SubchannelRef, unregisterChannelzRef } from './channelz'; +import { LoadBalancingCall } from './load-balancing-call'; +import { CallCredentials } from './call-credentials'; +import { Call, CallStreamOptions, StatusObject } from './call-interface'; +import { Deadline, deadlineToString } from './deadline'; +import { ResolvingCall } from './resolving-call'; +import { getNextCallNumber } from './call-number'; +import { restrictControlPlaneStatusCode } from './control-plane-status'; +import { MessageBufferTracker, RetryingCall, RetryThrottler } from './retrying-call'; +import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from './subchannel-interface'; + +/** + * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args + */ +const MAX_TIMEOUT_TIME = 2147483647; + +interface ConnectivityStateWatcher { + currentState: ConnectivityState; + timer: NodeJS.Timeout | null; + callback: (error?: Error) => void; +} + +interface NoneConfigResult { + type: 'NONE'; +} + +interface SuccessConfigResult { + type: 'SUCCESS'; + config: CallConfig; +} + +interface ErrorConfigResult { + type: 'ERROR'; + error: StatusObject; +} + +type GetConfigResult = NoneConfigResult | SuccessConfigResult | ErrorConfigResult; + +const RETRY_THROTTLER_MAP: Map = new Map(); + +const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1<<24; // 16 MB +const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1<<20; // 1 MB + +class ChannelSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { + private refCount = 0; + private subchannelStateListener: ConnectivityStateListener; + constructor(childSubchannel: SubchannelInterface, private channel: InternalChannel) { + super(childSubchannel); + this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime) => { + channel.throttleKeepalive(keepaliveTime); + }; + childSubchannel.addConnectivityStateListener(this.subchannelStateListener); + } + + ref(): void { + this.child.ref(); + this.refCount += 1; + } + + unref(): void { + this.child.unref(); + this.refCount -= 1; + if (this.refCount <= 0) { + this.child.removeConnectivityStateListener(this.subchannelStateListener); + this.channel.removeWrappedSubchannel(this); + } + } +} + +export class InternalChannel { + + private resolvingLoadBalancer: ResolvingLoadBalancer; + private subchannelPool: SubchannelPool; + private connectivityState: ConnectivityState = ConnectivityState.IDLE; + private currentPicker: Picker = new UnavailablePicker(); + /** + * Calls queued up to get a call config. Should only be populated before the + * first time the resolver returns a result, which includes the ConfigSelector. + */ + private configSelectionQueue: ResolvingCall[] = []; + private pickQueue: LoadBalancingCall[] = []; + private connectivityStateWatchers: ConnectivityStateWatcher[] = []; + private defaultAuthority: string; + private filterStackFactory: FilterStackFactory; + private target: GrpcUri; + /** + * This timer does not do anything on its own. Its purpose is to hold the + * event loop open while there are any pending calls for the channel that + * have not yet been assigned to specific subchannels. In other words, + * the invariant is that callRefTimer is reffed if and only if pickQueue + * is non-empty. + */ + private callRefTimer: NodeJS.Timeout; + private configSelector: ConfigSelector | null = null; + /** + * This is the error from the name resolver if it failed most recently. It + * is only used to end calls that start while there is no config selector + * and the name resolver is in backoff, so it should be nulled if + * configSelector becomes set or the channel state becomes anything other + * than TRANSIENT_FAILURE. + */ + private currentResolutionError: StatusObject | null = null; + private retryBufferTracker: MessageBufferTracker; + private keepaliveTime: number; + private wrappedSubchannels: Set = new Set(); + + // Channelz info + private readonly channelzEnabled: boolean = true; + private originalTarget: string; + private channelzRef: ChannelRef; + private channelzTrace: ChannelzTrace; + private callTracker = new ChannelzCallTracker(); + private childrenTracker = new ChannelzChildrenTracker(); + + constructor( + target: string, + private readonly credentials: ChannelCredentials, + private readonly options: ChannelOptions + ) { + if (typeof target !== 'string') { + throw new TypeError('Channel target must be a string'); + } + if (!(credentials instanceof ChannelCredentials)) { + throw new TypeError( + 'Channel credentials must be a ChannelCredentials object' + ); + } + if (options) { + if (typeof options !== 'object') { + throw new TypeError('Channel options must be an object'); + } + } + this.originalTarget = target; + const originalTargetUri = parseUri(target); + if (originalTargetUri === null) { + throw new Error(`Could not parse target name "${target}"`); + } + /* This ensures that the target has a scheme that is registered with the + * resolver */ + const defaultSchemeMapResult = mapUriDefaultScheme(originalTargetUri); + if (defaultSchemeMapResult === null) { + throw new Error( + `Could not find a default scheme for target name "${target}"` + ); + } + + this.callRefTimer = setInterval(() => {}, MAX_TIMEOUT_TIME); + this.callRefTimer.unref?.(); + + if (this.options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + + this.channelzTrace = new ChannelzTrace(); + this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo(), this.channelzEnabled); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Channel created'); + } + + if (this.options['grpc.default_authority']) { + this.defaultAuthority = this.options['grpc.default_authority'] as string; + } else { + this.defaultAuthority = getDefaultAuthority(defaultSchemeMapResult); + } + const proxyMapResult = mapProxyName(defaultSchemeMapResult, options); + this.target = proxyMapResult.target; + this.options = Object.assign({}, this.options, proxyMapResult.extraOptions); + + /* The global boolean parameter to getSubchannelPool has the inverse meaning to what + * the grpc.use_local_subchannel_pool channel option means. */ + this.subchannelPool = getSubchannelPool( + (options['grpc.use_local_subchannel_pool'] ?? 0) === 0 + ); + this.retryBufferTracker = new MessageBufferTracker( + options['grpc.retry_buffer_size'] ?? DEFAULT_RETRY_BUFFER_SIZE_BYTES, + options['grpc.per_rpc_retry_buffer_size'] ?? DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES + ); + this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; + const channelControlHelper: ChannelControlHelper = { + createSubchannel: ( + subchannelAddress: SubchannelAddress, + subchannelArgs: ChannelOptions + ) => { + const subchannel = this.subchannelPool.getOrCreateSubchannel( + this.target, + subchannelAddress, + Object.assign({}, this.options, subchannelArgs), + this.credentials + ); + subchannel.throttleKeepalive(this.keepaliveTime); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); + } + const wrappedSubchannel = new ChannelSubchannelWrapper(subchannel, this); + this.wrappedSubchannels.add(wrappedSubchannel); + return wrappedSubchannel; + }, + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + this.currentPicker = picker; + const queueCopy = this.pickQueue.slice(); + this.pickQueue = []; + this.callRefTimerUnref(); + for (const call of queueCopy) { + call.doPick(); + } + this.updateState(connectivityState); + }, + requestReresolution: () => { + // This should never be called. + throw new Error( + 'Resolving load balancer should never call requestReresolution' + ); + }, + addChannelzChild: (child: ChannelRef | SubchannelRef) => { + if (this.channelzEnabled) { + this.childrenTracker.refChild(child); + } + }, + removeChannelzChild: (child: ChannelRef | SubchannelRef) => { + if (this.channelzEnabled) { + this.childrenTracker.unrefChild(child); + } + } + }; + this.resolvingLoadBalancer = new ResolvingLoadBalancer( + this.target, + channelControlHelper, + options, + (serviceConfig, configSelector) => { + if (serviceConfig.retryThrottling) { + RETRY_THROTTLER_MAP.set(this.getTarget(), new RetryThrottler(serviceConfig.retryThrottling.maxTokens, serviceConfig.retryThrottling.tokenRatio, RETRY_THROTTLER_MAP.get(this.getTarget()))); + } else { + RETRY_THROTTLER_MAP.delete(this.getTarget()); + } + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); + } + this.configSelector = configSelector; + this.currentResolutionError = null; + /* We process the queue asynchronously to ensure that the corresponding + * load balancer update has completed. */ + process.nextTick(() => { + const localQueue = this.configSelectionQueue; + this.configSelectionQueue = []; + this.callRefTimerUnref(); + for (const call of localQueue) { + call.getConfig(); + } + this.configSelectionQueue = []; + }); + + }, + (status) => { + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); + } + if (this.configSelectionQueue.length > 0) { + this.trace('Name resolution failed with calls queued for config selection'); + } + if (this.configSelector === null) { + this.currentResolutionError = {...restrictControlPlaneStatusCode(status.code, status.details), metadata: status.metadata}; + } + const localQueue = this.configSelectionQueue; + this.configSelectionQueue = []; + this.callRefTimerUnref(); + for (const call of localQueue) { + call.reportResolverError(status); + } + } + ); + this.filterStackFactory = new FilterStackFactory([ + new CompressionFilterFactory(this, this.options), + ]); + this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2)); + const error = new Error(); + trace(LogVerbosity.DEBUG, 'channel_stacktrace', '(' + this.channelzRef.id + ') ' + 'Channel constructed \n' + error.stack?.substring(error.stack.indexOf('\n')+1)); + } + + private getChannelzInfo(): ChannelInfo { + return { + target: this.originalTarget, + state: this.connectivityState, + trace: this.channelzTrace, + callTracker: this.callTracker, + children: this.childrenTracker.getChildLists() + }; + } + + private trace(text: string, verbosityOverride?: LogVerbosity) { + trace(verbosityOverride ?? LogVerbosity.DEBUG, 'channel', '(' + this.channelzRef.id + ') ' + uriToString(this.target) + ' ' + text); + } + + private callRefTimerRef() { + // If the hasRef function does not exist, always run the code + if (!this.callRefTimer.hasRef?.()) { + this.trace( + 'callRefTimer.ref | configSelectionQueue.length=' + + this.configSelectionQueue.length + + ' pickQueue.length=' + + this.pickQueue.length + ); + this.callRefTimer.ref?.(); + } + } + + private callRefTimerUnref() { + // If the hasRef function does not exist, always run the code + if (!this.callRefTimer.hasRef || this.callRefTimer.hasRef()) { + this.trace( + 'callRefTimer.unref | configSelectionQueue.length=' + + this.configSelectionQueue.length + + ' pickQueue.length=' + + this.pickQueue.length + ); + this.callRefTimer.unref?.(); + } + } + + private removeConnectivityStateWatcher( + watcherObject: ConnectivityStateWatcher + ) { + const watcherIndex = this.connectivityStateWatchers.findIndex( + (value) => value === watcherObject + ); + if (watcherIndex >= 0) { + this.connectivityStateWatchers.splice(watcherIndex, 1); + } + } + + private updateState(newState: ConnectivityState): void { + trace( + LogVerbosity.DEBUG, + 'connectivity_state', + '(' + this.channelzRef.id + ') ' + + uriToString(this.target) + + ' ' + + ConnectivityState[this.connectivityState] + + ' -> ' + + ConnectivityState[newState] + ); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + } + this.connectivityState = newState; + const watchersCopy = this.connectivityStateWatchers.slice(); + for (const watcherObject of watchersCopy) { + if (newState !== watcherObject.currentState) { + if (watcherObject.timer) { + clearTimeout(watcherObject.timer); + } + this.removeConnectivityStateWatcher(watcherObject); + watcherObject.callback(); + } + } + if (newState !== ConnectivityState.TRANSIENT_FAILURE) { + this.currentResolutionError = null; + } + } + + throttleKeepalive(newKeepaliveTime: number) { + if (newKeepaliveTime > this.keepaliveTime) { + this.keepaliveTime = newKeepaliveTime; + for (const wrappedSubchannel of this.wrappedSubchannels) { + wrappedSubchannel.throttleKeepalive(newKeepaliveTime); + } + } + } + + removeWrappedSubchannel(wrappedSubchannel: ChannelSubchannelWrapper) { + this.wrappedSubchannels.delete(wrappedSubchannel); + } + + doPick(metadata: Metadata, extraPickInfo: {[key: string]: string}) { + return this.currentPicker.pick({metadata: metadata, extraPickInfo: extraPickInfo}); + } + + queueCallForPick(call: LoadBalancingCall) { + this.pickQueue.push(call); + this.callRefTimerRef(); + } + + getConfig(method: string, metadata: Metadata): GetConfigResult { + this.resolvingLoadBalancer.exitIdle(); + if (this.configSelector) { + return { + type: 'SUCCESS', + config: this.configSelector(method, metadata) + }; + } else { + if (this.currentResolutionError) { + return { + type: 'ERROR', + error: this.currentResolutionError + } + } else { + return { + type: 'NONE' + } + } + } + } + + queueCallForConfig(call: ResolvingCall) { + this.configSelectionQueue.push(call); + this.callRefTimerRef(); + } + + createLoadBalancingCall( + callConfig: CallConfig, + method: string, + host: string, + credentials: CallCredentials, + deadline: Deadline + ): LoadBalancingCall { + const callNumber = getNextCallNumber(); + this.trace( + 'createLoadBalancingCall [' + + callNumber + + '] method="' + + method + + '"' + ); + return new LoadBalancingCall(this, callConfig, method, host, credentials, deadline, callNumber); + } + + createRetryingCall( + callConfig: CallConfig, + method: string, + host: string, + credentials: CallCredentials, + deadline: Deadline + ): RetryingCall { + const callNumber = getNextCallNumber(); + this.trace( + 'createRetryingCall [' + + callNumber + + '] method="' + + method + + '"' + ); + return new RetryingCall(this, callConfig, method, host, credentials, deadline, callNumber, this.retryBufferTracker, RETRY_THROTTLER_MAP.get(this.getTarget())) + } + + createInnerCall( + callConfig: CallConfig, + method: string, + host: string, + credentials: CallCredentials, + deadline: Deadline + ): Call { + // Create a RetryingCall if retries are enabled + if (this.options['grpc.enable_retries'] === 0) { + return this.createLoadBalancingCall(callConfig, method, host, credentials, deadline); + } else { + return this.createRetryingCall(callConfig, method, host, credentials, deadline); + } + } + + createResolvingCall( + method: string, + deadline: Deadline, + host: string | null | undefined, + parentCall: ServerSurfaceCall | null, + propagateFlags: number | null | undefined + ): ResolvingCall { + const callNumber = getNextCallNumber(); + this.trace( + 'createResolvingCall [' + + callNumber + + '] method="' + + method + + '", deadline=' + + deadlineToString(deadline) + ); + const finalOptions: CallStreamOptions = { + deadline: deadline, + flags: propagateFlags ?? Propagate.DEFAULTS, + host: host ?? this.defaultAuthority, + parentCall: parentCall, + }; + + const call = new ResolvingCall(this, method, finalOptions, this.filterStackFactory.clone(), this.credentials._getCallCredentials(), callNumber); + + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + call.addStatusWatcher(status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); + } + return call; + + } + + close() { + this.resolvingLoadBalancer.destroy(); + this.updateState(ConnectivityState.SHUTDOWN); + clearInterval(this.callRefTimer); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } + + this.subchannelPool.unrefUnusedSubchannels(); + } + + getTarget() { + return uriToString(this.target); + } + + getConnectivityState(tryToConnect: boolean) { + const connectivityState = this.connectivityState; + if (tryToConnect) { + this.resolvingLoadBalancer.exitIdle(); + } + return connectivityState; + } + + watchConnectivityState( + currentState: ConnectivityState, + deadline: Date | number, + callback: (error?: Error) => void + ): void { + if (this.connectivityState === ConnectivityState.SHUTDOWN) { + throw new Error('Channel has been shut down'); + } + let timer = null; + if (deadline !== Infinity) { + const deadlineDate: Date = + deadline instanceof Date ? deadline : new Date(deadline); + const now = new Date(); + if (deadline === -Infinity || deadlineDate <= now) { + process.nextTick( + callback, + new Error('Deadline passed without connectivity state change') + ); + return; + } + timer = setTimeout(() => { + this.removeConnectivityStateWatcher(watcherObject); + callback( + new Error('Deadline passed without connectivity state change') + ); + }, deadlineDate.getTime() - now.getTime()); + } + const watcherObject = { + currentState, + callback, + timer, + }; + this.connectivityStateWatchers.push(watcherObject); + } + + /** + * Get the channelz reference object for this channel. The returned value is + * garbage if channelz is disabled for this channel. + * @returns + */ + getChannelzRef() { + return this.channelzRef; + } + + createCall( + method: string, + deadline: Deadline, + host: string | null | undefined, + parentCall: ServerSurfaceCall | null, + propagateFlags: number | null | undefined + ): Call { + if (typeof method !== 'string') { + throw new TypeError('Channel#createCall: method must be a string'); + } + if (!(typeof deadline === 'number' || deadline instanceof Date)) { + throw new TypeError( + 'Channel#createCall: deadline must be a number or Date' + ); + } + if (this.connectivityState === ConnectivityState.SHUTDOWN) { + throw new Error('Channel has been shut down'); + } + return this.createResolvingCall(method, deadline, host, parentCall, propagateFlags); + } +} diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts new file mode 100644 index 000000000..64b341810 --- /dev/null +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -0,0 +1,155 @@ +/* + * Copyright 2020 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. + * + */ + +import { + LoadBalancer, + ChannelControlHelper, + LoadBalancingConfig, + createLoadBalancer, +} from './load-balancer'; +import { SubchannelAddress } from './subchannel-address'; +import { ChannelOptions } from './channel-options'; +import { ConnectivityState } from './connectivity-state'; +import { Picker } from './picker'; +import { ChannelRef, SubchannelRef } from './channelz'; +import { SubchannelInterface } from './subchannel-interface'; + +const TYPE_NAME = 'child_load_balancer_helper'; + +export class ChildLoadBalancerHandler implements LoadBalancer { + private currentChild: LoadBalancer | null = null; + private pendingChild: LoadBalancer | null = null; + + private ChildPolicyHelper = class { + private child: LoadBalancer | null = null; + constructor(private parent: ChildLoadBalancerHandler) {} + createSubchannel( + subchannelAddress: SubchannelAddress, + subchannelArgs: ChannelOptions + ): SubchannelInterface { + return this.parent.channelControlHelper.createSubchannel( + subchannelAddress, + subchannelArgs + ); + } + updateState(connectivityState: ConnectivityState, picker: Picker): void { + if (this.calledByPendingChild()) { + if (connectivityState === ConnectivityState.CONNECTING) { + return; + } + this.parent.currentChild?.destroy(); + this.parent.currentChild = this.parent.pendingChild; + this.parent.pendingChild = null; + } else if (!this.calledByCurrentChild()) { + return; + } + this.parent.channelControlHelper.updateState(connectivityState, picker); + } + requestReresolution(): void { + const latestChild = this.parent.pendingChild ?? this.parent.currentChild; + if (this.child === latestChild) { + this.parent.channelControlHelper.requestReresolution(); + } + } + setChild(newChild: LoadBalancer) { + this.child = newChild; + } + addChannelzChild(child: ChannelRef | SubchannelRef) { + this.parent.channelControlHelper.addChannelzChild(child); + } + removeChannelzChild(child: ChannelRef | SubchannelRef) { + this.parent.channelControlHelper.removeChannelzChild(child); + } + + private calledByPendingChild(): boolean { + return this.child === this.parent.pendingChild; + } + private calledByCurrentChild(): boolean { + return this.child === this.parent.currentChild; + } + }; + + constructor(private readonly channelControlHelper: ChannelControlHelper) {} + + /** + * Prerequisites: lbConfig !== null and lbConfig.name is registered + * @param addressList + * @param lbConfig + * @param attributes + */ + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void { + let childToUpdate: LoadBalancer; + if ( + this.currentChild === null || + this.currentChild.getTypeName() !== lbConfig.getLoadBalancerName() + ) { + const newHelper = new this.ChildPolicyHelper(this); + const newChild = createLoadBalancer(lbConfig, newHelper)!; + newHelper.setChild(newChild); + if (this.currentChild === null) { + this.currentChild = newChild; + childToUpdate = this.currentChild; + } else { + if (this.pendingChild) { + this.pendingChild.destroy(); + } + this.pendingChild = newChild; + childToUpdate = this.pendingChild; + } + } else { + if (this.pendingChild === null) { + childToUpdate = this.currentChild; + } else { + childToUpdate = this.pendingChild; + } + } + childToUpdate.updateAddressList(addressList, lbConfig, attributes); + } + exitIdle(): void { + if (this.currentChild) { + this.currentChild.exitIdle(); + if (this.pendingChild) { + this.pendingChild.exitIdle(); + } + } + } + resetBackoff(): void { + if (this.currentChild) { + this.currentChild.resetBackoff(); + if (this.pendingChild) { + this.pendingChild.resetBackoff(); + } + } + } + destroy(): void { + if (this.currentChild) { + this.currentChild.destroy(); + this.currentChild = null; + } + if (this.pendingChild) { + this.pendingChild.destroy(); + this.pendingChild = null; + } + } + getTypeName(): string { + return TYPE_NAME; + } +} diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts new file mode 100644 index 000000000..2cba9d14a --- /dev/null +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -0,0 +1,660 @@ +/* + * Copyright 2022 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. + * + */ + +import { ChannelOptions } from "./channel-options"; +import { ConnectivityState } from "./connectivity-state"; +import { LogVerbosity, Status } from "./constants"; +import { durationToMs, isDuration, msToDuration } from "./duration"; +import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType } from "./experimental"; +import { getFirstUsableConfig, LoadBalancer, LoadBalancingConfig, validateLoadBalancingConfig } from "./load-balancer"; +import { ChildLoadBalancerHandler } from "./load-balancer-child-handler"; +import { PickArgs, Picker, PickResult, PickResultType } from "./picker"; +import { SubchannelAddress, subchannelAddressToString } from "./subchannel-address"; +import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from "./subchannel-interface"; +import * as logging from './logging'; + +const TRACER_NAME = 'outlier_detection'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'outlier_detection'; + +const OUTLIER_DETECTION_ENABLED = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; + +export interface SuccessRateEjectionConfig { + readonly stdev_factor: number; + readonly enforcement_percentage: number; + readonly minimum_hosts: number; + readonly request_volume: number; +} + +export interface FailurePercentageEjectionConfig { + readonly threshold: number; + readonly enforcement_percentage: number; + readonly minimum_hosts: number; + readonly request_volume: number; +} + +const defaultSuccessRateEjectionConfig: SuccessRateEjectionConfig = { + stdev_factor: 1900, + enforcement_percentage: 100, + minimum_hosts: 5, + request_volume: 100 +}; + +const defaultFailurePercentageEjectionConfig: FailurePercentageEjectionConfig = { + threshold: 85, + enforcement_percentage: 100, + minimum_hosts: 5, + request_volume: 50 +} + +type TypeofValues = 'object' | 'boolean' | 'function' | 'number' | 'string' | 'undefined'; + +function validateFieldType(obj: any, fieldName: string, expectedType: TypeofValues, objectName?: string) { + if (fieldName in obj && typeof obj[fieldName] !== expectedType) { + const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; + throw new Error(`outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[fieldName]}`); + } +} + +function validatePositiveDuration(obj: any, fieldName: string, objectName?: string) { + const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; + if (fieldName in obj) { + if (!isDuration(obj[fieldName])) { + throw new Error(`outlier detection config ${fullFieldName} parse error: expected Duration, got ${typeof obj[fieldName]}`); + } + if (!(obj[fieldName].seconds >= 0 && obj[fieldName].seconds <= 315_576_000_000 && obj[fieldName].nanos >= 0 && obj[fieldName].nanos <= 999_999_999)) { + throw new Error(`outlier detection config ${fullFieldName} parse error: values out of range for non-negative Duaration`); + } + } +} + +function validatePercentage(obj: any, fieldName: string, objectName?: string) { + const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; + validateFieldType(obj, fieldName, 'number', objectName); + if (fieldName in obj && !(obj[fieldName] >= 0 && obj[fieldName] <= 100)) { + throw new Error(`outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)`); + } +} + +export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig { + private readonly intervalMs: number; + private readonly baseEjectionTimeMs: number; + private readonly maxEjectionTimeMs: number; + private readonly maxEjectionPercent: number; + private readonly successRateEjection: SuccessRateEjectionConfig | null; + private readonly failurePercentageEjection: FailurePercentageEjectionConfig | null; + + constructor( + intervalMs: number | null, + baseEjectionTimeMs: number | null, + maxEjectionTimeMs: number | null, + maxEjectionPercent: number | null, + successRateEjection: Partial | null, + failurePercentageEjection: Partial | null, + private readonly childPolicy: LoadBalancingConfig[] + ) { + if (childPolicy.length > 0 && childPolicy[0].getLoadBalancerName() === 'pick_first') { + throw new Error('outlier_detection LB policy cannot have a pick_first child policy'); + } + this.intervalMs = intervalMs ?? 10_000; + this.baseEjectionTimeMs = baseEjectionTimeMs ?? 30_000; + this.maxEjectionTimeMs = maxEjectionTimeMs ?? 300_000; + this.maxEjectionPercent = maxEjectionPercent ?? 10; + this.successRateEjection = successRateEjection ? {...defaultSuccessRateEjectionConfig, ...successRateEjection} : null; + this.failurePercentageEjection = failurePercentageEjection ? {...defaultFailurePercentageEjectionConfig, ...failurePercentageEjection}: null; + } + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + return { + interval: msToDuration(this.intervalMs), + base_ejection_time: msToDuration(this.baseEjectionTimeMs), + max_ejection_time: msToDuration(this.maxEjectionTimeMs), + max_ejection_percent: this.maxEjectionPercent, + success_rate_ejection: this.successRateEjection, + failure_percentage_ejection: this.failurePercentageEjection, + child_policy: this.childPolicy.map(policy => policy.toJsonObject()) + }; + } + + getIntervalMs(): number { + return this.intervalMs; + } + getBaseEjectionTimeMs(): number { + return this.baseEjectionTimeMs; + } + getMaxEjectionTimeMs(): number { + return this.maxEjectionTimeMs; + } + getMaxEjectionPercent(): number { + return this.maxEjectionPercent; + } + getSuccessRateEjectionConfig(): SuccessRateEjectionConfig | null { + return this.successRateEjection; + } + getFailurePercentageEjectionConfig(): FailurePercentageEjectionConfig | null { + return this.failurePercentageEjection; + } + getChildPolicy(): LoadBalancingConfig[] { + return this.childPolicy; + } + + copyWithChildPolicy(childPolicy: LoadBalancingConfig[]): OutlierDetectionLoadBalancingConfig { + return new OutlierDetectionLoadBalancingConfig(this.intervalMs, this.baseEjectionTimeMs, this.maxEjectionTimeMs, this.maxEjectionPercent, this.successRateEjection, this.failurePercentageEjection, childPolicy); + } + + static createFromJson(obj: any): OutlierDetectionLoadBalancingConfig { + validatePositiveDuration(obj, 'interval'); + validatePositiveDuration(obj, 'base_ejection_time'); + validatePositiveDuration(obj, 'max_ejection_time'); + validatePercentage(obj, 'max_ejection_percent'); + if ('success_rate_ejection' in obj) { + if (typeof obj.success_rate_ejection !== 'object') { + throw new Error('outlier detection config success_rate_ejection must be an object'); + } + validateFieldType(obj.success_rate_ejection, 'stdev_factor', 'number', 'success_rate_ejection'); + validatePercentage(obj.success_rate_ejection, 'enforcement_percentage', 'success_rate_ejection'); + validateFieldType(obj.success_rate_ejection, 'minimum_hosts', 'number', 'success_rate_ejection'); + validateFieldType(obj.success_rate_ejection, 'request_volume', 'number', 'success_rate_ejection'); + } + if ('failure_percentage_ejection' in obj) { + if (typeof obj.failure_percentage_ejection !== 'object') { + throw new Error('outlier detection config failure_percentage_ejection must be an object'); + } + validatePercentage(obj.failure_percentage_ejection, 'threshold', 'failure_percentage_ejection'); + validatePercentage(obj.failure_percentage_ejection, 'enforcement_percentage', 'failure_percentage_ejection'); + validateFieldType(obj.failure_percentage_ejection, 'minimum_hosts', 'number', 'failure_percentage_ejection'); + validateFieldType(obj.failure_percentage_ejection, 'request_volume', 'number', 'failure_percentage_ejection'); + } + + return new OutlierDetectionLoadBalancingConfig( + obj.interval ? durationToMs(obj.interval) : null, + obj.base_ejection_time ? durationToMs(obj.base_ejection_time) : null, + obj.max_ejection_time ? durationToMs(obj.max_ejection_time) : null, + obj.max_ejection_percent ?? null, + obj.success_rate_ejection, + obj.failure_percentage_ejection, + obj.child_policy.map(validateLoadBalancingConfig) + ); + } +} + +class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { + private childSubchannelState: ConnectivityState; + private stateListeners: ConnectivityStateListener[] = []; + private ejected: boolean = false; + private refCount: number = 0; + constructor(childSubchannel: SubchannelInterface, private mapEntry?: MapEntry) { + super(childSubchannel); + this.childSubchannelState = childSubchannel.getConnectivityState(); + childSubchannel.addConnectivityStateListener((subchannel, previousState, newState, keepaliveTime) => { + this.childSubchannelState = newState; + if (!this.ejected) { + for (const listener of this.stateListeners) { + listener(this, previousState, newState, keepaliveTime); + } + } + }); + } + + getConnectivityState(): ConnectivityState { + if (this.ejected) { + return ConnectivityState.TRANSIENT_FAILURE; + } else { + return this.childSubchannelState; + } + } + + /** + * Add a listener function to be called whenever the wrapper's + * connectivity state changes. + * @param listener + */ + addConnectivityStateListener(listener: ConnectivityStateListener) { + this.stateListeners.push(listener); + } + + /** + * Remove a listener previously added with `addConnectivityStateListener` + * @param listener A reference to a function previously passed to + * `addConnectivityStateListener` + */ + removeConnectivityStateListener(listener: ConnectivityStateListener) { + const listenerIndex = this.stateListeners.indexOf(listener); + if (listenerIndex > -1) { + this.stateListeners.splice(listenerIndex, 1); + } + } + + ref() { + this.child.ref(); + this.refCount += 1; + } + + unref() { + this.child.unref(); + this.refCount -= 1; + if (this.refCount <= 0) { + if (this.mapEntry) { + const index = this.mapEntry.subchannelWrappers.indexOf(this); + if (index >= 0) { + this.mapEntry.subchannelWrappers.splice(index, 1); + } + } + } + } + + eject() { + this.ejected = true; + for (const listener of this.stateListeners) { + listener(this, this.childSubchannelState, ConnectivityState.TRANSIENT_FAILURE, -1); + } + } + + uneject() { + this.ejected = false; + for (const listener of this.stateListeners) { + listener(this, ConnectivityState.TRANSIENT_FAILURE, this.childSubchannelState, -1); + } + } + + getMapEntry(): MapEntry | undefined { + return this.mapEntry; + } + + getWrappedSubchannel(): SubchannelInterface { + return this.child; + } +} + +interface CallCountBucket { + success: number; + failure: number; +} + +function createEmptyBucket(): CallCountBucket { + return { + success: 0, + failure: 0 + } +} + +class CallCounter { + private activeBucket: CallCountBucket = createEmptyBucket(); + private inactiveBucket: CallCountBucket = createEmptyBucket(); + addSuccess() { + this.activeBucket.success += 1; + } + addFailure() { + this.activeBucket.failure += 1; + } + switchBuckets() { + this.inactiveBucket = this.activeBucket; + this.activeBucket = createEmptyBucket(); + } + getLastSuccesses() { + return this.inactiveBucket.success; + } + getLastFailures() { + return this.inactiveBucket.failure; + } +} + +interface MapEntry { + counter: CallCounter; + currentEjectionTimestamp: Date | null; + ejectionTimeMultiplier: number; + subchannelWrappers: OutlierDetectionSubchannelWrapper[]; +} + +class OutlierDetectionPicker implements Picker { + constructor(private wrappedPicker: Picker, private countCalls: boolean) {} + pick(pickArgs: PickArgs): PickResult { + const wrappedPick = this.wrappedPicker.pick(pickArgs); + if (wrappedPick.pickResultType === PickResultType.COMPLETE) { + const subchannelWrapper = wrappedPick.subchannel as OutlierDetectionSubchannelWrapper; + const mapEntry = subchannelWrapper.getMapEntry(); + if (mapEntry) { + let onCallEnded = wrappedPick.onCallEnded; + if (this.countCalls) { + onCallEnded = statusCode => { + if (statusCode === Status.OK) { + mapEntry.counter.addSuccess(); + } else { + mapEntry.counter.addFailure(); + } + wrappedPick.onCallEnded?.(statusCode); + }; + } + return { + ...wrappedPick, + subchannel: subchannelWrapper.getWrappedSubchannel(), + onCallEnded: onCallEnded + }; + } else { + return { + ...wrappedPick, + subchannel: subchannelWrapper.getWrappedSubchannel() + } + } + } else { + return wrappedPick; + } + } + +} + +export class OutlierDetectionLoadBalancer implements LoadBalancer { + private childBalancer: ChildLoadBalancerHandler; + private addressMap: Map = new Map(); + private latestConfig: OutlierDetectionLoadBalancingConfig | null = null; + private ejectionTimer: NodeJS.Timeout; + private timerStartTime: Date | null = null; + + constructor(channelControlHelper: ChannelControlHelper) { + this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { + createSubchannel: (subchannelAddress: SubchannelAddress, subchannelArgs: ChannelOptions) => { + const originalSubchannel = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); + const mapEntry = this.addressMap.get(subchannelAddressToString(subchannelAddress)); + const subchannelWrapper = new OutlierDetectionSubchannelWrapper(originalSubchannel, mapEntry); + if (mapEntry?.currentEjectionTimestamp !== null) { + // If the address is ejected, propagate that to the new subchannel wrapper + subchannelWrapper.eject(); + } + mapEntry?.subchannelWrappers.push(subchannelWrapper); + return subchannelWrapper; + }, + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + if (connectivityState === ConnectivityState.READY) { + channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled())); + } else { + channelControlHelper.updateState(connectivityState, picker); + } + } + })); + this.ejectionTimer = setInterval(() => {}, 0); + clearInterval(this.ejectionTimer); + } + + private isCountingEnabled(): boolean { + return this.latestConfig !== null && + (this.latestConfig.getSuccessRateEjectionConfig() !== null || + this.latestConfig.getFailurePercentageEjectionConfig() !== null); + } + + private getCurrentEjectionPercent() { + let ejectionCount = 0; + for (const mapEntry of this.addressMap.values()) { + if (mapEntry.currentEjectionTimestamp !== null) { + ejectionCount += 1; + } + } + return (ejectionCount * 100) / this.addressMap.size; + } + + private runSuccessRateCheck(ejectionTimestamp: Date) { + if (!this.latestConfig) { + return; + } + const successRateConfig = this.latestConfig.getSuccessRateEjectionConfig(); + if (!successRateConfig) { + return; + } + trace('Running success rate check'); + // Step 1 + const targetRequestVolume = successRateConfig.request_volume; + let addresesWithTargetVolume = 0; + const successRates: number[] = [] + for (const [address, mapEntry] of this.addressMap) { + const successes = mapEntry.counter.getLastSuccesses(); + const failures = mapEntry.counter.getLastFailures(); + trace('Stats for ' + address + ': successes=' + successes + ' failures=' + failures + ' targetRequestVolume=' + targetRequestVolume); + if (successes + failures >= targetRequestVolume) { + addresesWithTargetVolume += 1; + successRates.push(successes/(successes + failures)); + } + } + trace('Found ' + addresesWithTargetVolume + ' success rate candidates; currentEjectionPercent=' + this.getCurrentEjectionPercent() + ' successRates=[' + successRates + ']'); + if (addresesWithTargetVolume < successRateConfig.minimum_hosts) { + return; + } + + // Step 2 + const successRateMean = successRates.reduce((a, b) => a + b) / successRates.length; + let successRateDeviationSum = 0; + for (const rate of successRates) { + const deviation = rate - successRateMean; + successRateDeviationSum += deviation * deviation; + } + const successRateVariance = successRateDeviationSum / successRates.length; + const successRateStdev = Math.sqrt(successRateVariance); + const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000); + trace('stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold); + + // Step 3 + for (const [address, mapEntry] of this.addressMap.entries()) { + // Step 3.i + if (this.getCurrentEjectionPercent() >= this.latestConfig.getMaxEjectionPercent()) { + break; + } + // Step 3.ii + const successes = mapEntry.counter.getLastSuccesses(); + const failures = mapEntry.counter.getLastFailures(); + if (successes + failures < targetRequestVolume) { + continue; + } + // Step 3.iii + const successRate = successes / (successes + failures); + trace('Checking candidate ' + address + ' successRate=' + successRate); + if (successRate < ejectionThreshold) { + const randomNumber = Math.random() * 100; + trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + successRateConfig.enforcement_percentage); + if (randomNumber < successRateConfig.enforcement_percentage) { + trace('Ejecting candidate ' + address); + this.eject(mapEntry, ejectionTimestamp); + } + } + } + } + + private runFailurePercentageCheck(ejectionTimestamp: Date) { + if (!this.latestConfig) { + return; + } + const failurePercentageConfig = this.latestConfig.getFailurePercentageEjectionConfig() + if (!failurePercentageConfig) { + return; + } + trace('Running failure percentage check. threshold=' + failurePercentageConfig.threshold + ' request volume threshold=' + failurePercentageConfig.request_volume); + // Step 1 + let addressesWithTargetVolume = 0; + for (const mapEntry of this.addressMap.values()) { + const successes = mapEntry.counter.getLastSuccesses(); + const failures = mapEntry.counter.getLastFailures(); + if (successes + failures >= failurePercentageConfig.request_volume) { + addressesWithTargetVolume += 1; + } + } + if (addressesWithTargetVolume < failurePercentageConfig.minimum_hosts) { + return; + } + + // Step 2 + for (const [address, mapEntry] of this.addressMap.entries()) { + // Step 2.i + if (this.getCurrentEjectionPercent() >= this.latestConfig.getMaxEjectionPercent()) { + break; + } + // Step 2.ii + const successes = mapEntry.counter.getLastSuccesses(); + const failures = mapEntry.counter.getLastFailures(); + trace('Candidate successes=' + successes + ' failures=' + failures); + if (successes + failures < failurePercentageConfig.request_volume) { + continue; + } + // Step 2.iii + const failurePercentage = (failures * 100) / (failures + successes); + if (failurePercentage > failurePercentageConfig.threshold) { + const randomNumber = Math.random() * 100; + trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + failurePercentageConfig.enforcement_percentage); + if (randomNumber < failurePercentageConfig.enforcement_percentage) { + trace('Ejecting candidate ' + address); + this.eject(mapEntry, ejectionTimestamp); + } + } + } + } + + private eject(mapEntry: MapEntry, ejectionTimestamp: Date) { + mapEntry.currentEjectionTimestamp = new Date(); + mapEntry.ejectionTimeMultiplier += 1; + for (const subchannelWrapper of mapEntry.subchannelWrappers) { + subchannelWrapper.eject(); + } + } + + private uneject(mapEntry: MapEntry) { + mapEntry.currentEjectionTimestamp = null; + for (const subchannelWrapper of mapEntry.subchannelWrappers) { + subchannelWrapper.uneject(); + } + } + + private switchAllBuckets() { + for (const mapEntry of this.addressMap.values()) { + mapEntry.counter.switchBuckets(); + } + } + + private startTimer(delayMs: number) { + this.ejectionTimer = setTimeout(() => this.runChecks(), delayMs); + this.ejectionTimer.unref?.(); + } + + private runChecks() { + const ejectionTimestamp = new Date(); + trace('Ejection timer running'); + + this.switchAllBuckets(); + + if (!this.latestConfig) { + return; + } + this.timerStartTime = ejectionTimestamp; + this.startTimer(this.latestConfig.getIntervalMs()); + + this.runSuccessRateCheck(ejectionTimestamp); + this.runFailurePercentageCheck(ejectionTimestamp); + + for (const [address, mapEntry] of this.addressMap.entries()) { + if (mapEntry.currentEjectionTimestamp === null) { + if (mapEntry.ejectionTimeMultiplier > 0) { + mapEntry.ejectionTimeMultiplier -= 1; + } + } else { + const baseEjectionTimeMs = this.latestConfig.getBaseEjectionTimeMs(); + const maxEjectionTimeMs = this.latestConfig.getMaxEjectionTimeMs(); + const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime()); + returnTime.setMilliseconds(returnTime.getMilliseconds() + Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs))); + if (returnTime < new Date()) { + trace('Unejecting ' + address); + this.uneject(mapEntry); + } + } + } + } + + updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) { + return; + } + const subchannelAddresses = new Set(); + for (const address of addressList) { + subchannelAddresses.add(subchannelAddressToString(address)); + } + for (const address of subchannelAddresses) { + if (!this.addressMap.has(address)) { + trace('Adding map entry for ' + address); + this.addressMap.set(address, { + counter: new CallCounter(), + currentEjectionTimestamp: null, + ejectionTimeMultiplier: 0, + subchannelWrappers: [] + }); + } + } + for (const key of this.addressMap.keys()) { + if (!subchannelAddresses.has(key)) { + trace('Removing map entry for ' + key); + this.addressMap.delete(key); + } + } + const childPolicy: LoadBalancingConfig = getFirstUsableConfig( + lbConfig.getChildPolicy(), + true + ); + this.childBalancer.updateAddressList(addressList, childPolicy, attributes); + + if (lbConfig.getSuccessRateEjectionConfig() || lbConfig.getFailurePercentageEjectionConfig()) { + if (this.timerStartTime) { + trace('Previous timer existed. Replacing timer'); + clearTimeout(this.ejectionTimer); + const remainingDelay = lbConfig.getIntervalMs() - ((new Date()).getTime() - this.timerStartTime.getTime()); + this.startTimer(remainingDelay); + } else { + trace('Starting new timer'); + this.timerStartTime = new Date(); + this.startTimer(lbConfig.getIntervalMs()); + this.switchAllBuckets(); + } + } else { + trace('Counting disabled. Cancelling timer.'); + this.timerStartTime = null; + clearTimeout(this.ejectionTimer); + for (const mapEntry of this.addressMap.values()) { + this.uneject(mapEntry); + mapEntry.ejectionTimeMultiplier = 0; + } + } + + this.latestConfig = lbConfig; + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + clearTimeout(this.ejectionTimer); + this.childBalancer.destroy(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + if (OUTLIER_DETECTION_ENABLED) { + registerLoadBalancerType(TYPE_NAME, OutlierDetectionLoadBalancer, OutlierDetectionLoadBalancingConfig); + } +} diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts new file mode 100644 index 000000000..41d21a2ea --- /dev/null +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -0,0 +1,482 @@ +/* + * Copyright 2019 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. + * + */ + +import { + LoadBalancer, + ChannelControlHelper, + LoadBalancingConfig, + registerDefaultLoadBalancerType, + registerLoadBalancerType, +} from './load-balancer'; +import { ConnectivityState } from './connectivity-state'; +import { + QueuePicker, + Picker, + PickArgs, + CompletePickResult, + PickResultType, + UnavailablePicker, +} from './picker'; +import { + SubchannelAddress, + subchannelAddressEqual, + subchannelAddressToString, +} from './subchannel-address'; +import * as logging from './logging'; +import { LogVerbosity } from './constants'; +import { SubchannelInterface, ConnectivityStateListener } from './subchannel-interface'; + +const TRACER_NAME = 'pick_first'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'pick_first'; + +/** + * Delay after starting a connection on a subchannel before starting a + * connection on the next subchannel in the list, for Happy Eyeballs algorithm. + */ +const CONNECTION_DELAY_INTERVAL_MS = 250; + +export class PickFirstLoadBalancingConfig implements LoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + + constructor() {} + + toJsonObject(): object { + return { + [TYPE_NAME]: {}, + }; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static createFromJson(obj: any) { + return new PickFirstLoadBalancingConfig(); + } +} + +/** + * Picker for a `PickFirstLoadBalancer` in the READY state. Always returns the + * picked subchannel. + */ +class PickFirstPicker implements Picker { + constructor(private subchannel: SubchannelInterface) {} + + pick(pickArgs: PickArgs): CompletePickResult { + return { + pickResultType: PickResultType.COMPLETE, + subchannel: this.subchannel, + status: null, + onCallStarted: null, + onCallEnded: null + }; + } +} + +interface ConnectivityStateCounts { + [ConnectivityState.CONNECTING]: number; + [ConnectivityState.IDLE]: number; + [ConnectivityState.READY]: number; + [ConnectivityState.SHUTDOWN]: number; + [ConnectivityState.TRANSIENT_FAILURE]: number; +} + +export class PickFirstLoadBalancer implements LoadBalancer { + /** + * The list of backend addresses most recently passed to `updateAddressList`. + */ + private latestAddressList: SubchannelAddress[] = []; + /** + * The list of subchannels this load balancer is currently attempting to + * connect to. + */ + private subchannels: SubchannelInterface[] = []; + /** + * The current connectivity state of the load balancer. + */ + private currentState: ConnectivityState = ConnectivityState.IDLE; + /** + * The index within the `subchannels` array of the subchannel with the most + * recently started connection attempt. + */ + private currentSubchannelIndex = 0; + + private subchannelStateCounts: ConnectivityStateCounts; + /** + * The currently picked subchannel used for making calls. Populated if + * and only if the load balancer's current state is READY. In that case, + * the subchannel's current state is also READY. + */ + private currentPick: SubchannelInterface | null = null; + /** + * Listener callback attached to each subchannel in the `subchannels` list + * while establishing a connection. + */ + private subchannelStateListener: ConnectivityStateListener; + /** + * Listener callback attached to the current picked subchannel. + */ + private pickedSubchannelStateListener: ConnectivityStateListener; + /** + * Timer reference for the timer tracking when to start + */ + private connectionDelayTimeout: NodeJS.Timeout; + + private triedAllSubchannels = false; + + /** + * Load balancer that attempts to connect to each backend in the address list + * in order, and picks the first one that connects, using it for every + * request. + * @param channelControlHelper `ChannelControlHelper` instance provided by + * this load balancer's owner. + */ + constructor(private readonly channelControlHelper: ChannelControlHelper) { + this.subchannelStateCounts = { + [ConnectivityState.CONNECTING]: 0, + [ConnectivityState.IDLE]: 0, + [ConnectivityState.READY]: 0, + [ConnectivityState.SHUTDOWN]: 0, + [ConnectivityState.TRANSIENT_FAILURE]: 0, + }; + this.subchannelStateListener = ( + subchannel: SubchannelInterface, + previousState: ConnectivityState, + newState: ConnectivityState + ) => { + this.subchannelStateCounts[previousState] -= 1; + this.subchannelStateCounts[newState] += 1; + /* If the subchannel we most recently attempted to start connecting + * to goes into TRANSIENT_FAILURE, immediately try to start + * connecting to the next one instead of waiting for the connection + * delay timer. */ + if ( + subchannel.getRealSubchannel() === this.subchannels[this.currentSubchannelIndex].getRealSubchannel() && + newState === ConnectivityState.TRANSIENT_FAILURE + ) { + this.startNextSubchannelConnecting(); + } + if (newState === ConnectivityState.READY) { + this.pickSubchannel(subchannel); + return; + } else { + if ( + this.triedAllSubchannels && + this.subchannelStateCounts[ConnectivityState.IDLE] === + this.subchannels.length + ) { + /* If all of the subchannels are IDLE we should go back to a + * basic IDLE state where there is no subchannel list to avoid + * holding unused resources. We do not reset triedAllSubchannels + * because that is a reminder to request reresolution the next time + * this LB policy needs to connect. */ + this.resetSubchannelList(false); + this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); + return; + } + if (this.currentPick === null) { + if (this.triedAllSubchannels) { + let newLBState: ConnectivityState; + if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) { + newLBState = ConnectivityState.CONNECTING; + } else if ( + this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > + 0 + ) { + newLBState = ConnectivityState.TRANSIENT_FAILURE; + } else { + newLBState = ConnectivityState.IDLE; + } + if (newLBState !== this.currentState) { + if (newLBState === ConnectivityState.TRANSIENT_FAILURE) { + this.updateState(newLBState, new UnavailablePicker()); + } else { + this.updateState(newLBState, new QueuePicker(this)); + } + } + } else { + this.updateState( + ConnectivityState.CONNECTING, + new QueuePicker(this) + ); + } + } + } + }; + this.pickedSubchannelStateListener = ( + subchannel: SubchannelInterface, + previousState: ConnectivityState, + newState: ConnectivityState + ) => { + if (newState !== ConnectivityState.READY) { + this.currentPick = null; + subchannel.unref(); + subchannel.removeConnectivityStateListener( + this.pickedSubchannelStateListener + ); + this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); + if (this.subchannels.length > 0) { + if (this.triedAllSubchannels) { + let newLBState: ConnectivityState; + if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) { + newLBState = ConnectivityState.CONNECTING; + } else if ( + this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > + 0 + ) { + newLBState = ConnectivityState.TRANSIENT_FAILURE; + } else { + newLBState = ConnectivityState.IDLE; + } + if (newLBState === ConnectivityState.TRANSIENT_FAILURE) { + this.updateState(newLBState, new UnavailablePicker()); + } else { + this.updateState(newLBState, new QueuePicker(this)); + } + } else { + this.updateState( + ConnectivityState.CONNECTING, + new QueuePicker(this) + ); + } + } else { + /* We don't need to backoff here because this only happens if a + * subchannel successfully connects then disconnects, so it will not + * create a loop of attempting to connect to an unreachable backend + */ + this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); + } + } + }; + this.connectionDelayTimeout = setTimeout(() => {}, 0); + clearTimeout(this.connectionDelayTimeout); + } + + private startNextSubchannelConnecting() { + if (this.triedAllSubchannels) { + return; + } + for (const [index, subchannel] of this.subchannels.entries()) { + if (index > this.currentSubchannelIndex) { + const subchannelState = subchannel.getConnectivityState(); + if ( + subchannelState === ConnectivityState.IDLE || + subchannelState === ConnectivityState.CONNECTING + ) { + this.startConnecting(index); + return; + } + } + } + this.triedAllSubchannels = true; + } + + /** + * Have a single subchannel in the `subchannels` list start connecting. + * @param subchannelIndex The index into the `subchannels` list. + */ + private startConnecting(subchannelIndex: number) { + clearTimeout(this.connectionDelayTimeout); + this.currentSubchannelIndex = subchannelIndex; + if ( + this.subchannels[subchannelIndex].getConnectivityState() === + ConnectivityState.IDLE + ) { + trace( + 'Start connecting to subchannel with address ' + + this.subchannels[subchannelIndex].getAddress() + ); + process.nextTick(() => { + this.subchannels[subchannelIndex].startConnecting(); + }); + } + this.connectionDelayTimeout = setTimeout(() => { + this.startNextSubchannelConnecting(); + }, CONNECTION_DELAY_INTERVAL_MS); + } + + private pickSubchannel(subchannel: SubchannelInterface) { + trace('Pick subchannel with address ' + subchannel.getAddress()); + if (this.currentPick !== null) { + this.currentPick.unref(); + this.currentPick.removeConnectivityStateListener( + this.pickedSubchannelStateListener + ); + } + this.currentPick = subchannel; + subchannel.addConnectivityStateListener(this.pickedSubchannelStateListener); + subchannel.ref(); + this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); + this.resetSubchannelList(); + clearTimeout(this.connectionDelayTimeout); + this.updateState(ConnectivityState.READY, new PickFirstPicker(subchannel)); + } + + private updateState(newState: ConnectivityState, picker: Picker) { + trace( + ConnectivityState[this.currentState] + + ' -> ' + + ConnectivityState[newState] + ); + this.currentState = newState; + this.channelControlHelper.updateState(newState, picker); + } + + private resetSubchannelList(resetTriedAllSubchannels = true) { + for (const subchannel of this.subchannels) { + subchannel.removeConnectivityStateListener(this.subchannelStateListener); + subchannel.unref(); + this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); + } + this.currentSubchannelIndex = 0; + this.subchannelStateCounts = { + [ConnectivityState.CONNECTING]: 0, + [ConnectivityState.IDLE]: 0, + [ConnectivityState.READY]: 0, + [ConnectivityState.SHUTDOWN]: 0, + [ConnectivityState.TRANSIENT_FAILURE]: 0, + }; + this.subchannels = []; + if (resetTriedAllSubchannels) { + this.triedAllSubchannels = false; + } + } + + /** + * Start connecting to the address list most recently passed to + * `updateAddressList`. + */ + private connectToAddressList(): void { + this.resetSubchannelList(); + trace( + 'Connect to address list ' + + this.latestAddressList.map((address) => + subchannelAddressToString(address) + ) + ); + this.subchannels = this.latestAddressList.map((address) => + this.channelControlHelper.createSubchannel(address, {}) + ); + for (const subchannel of this.subchannels) { + subchannel.ref(); + this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); + } + for (const subchannel of this.subchannels) { + subchannel.addConnectivityStateListener(this.subchannelStateListener); + this.subchannelStateCounts[subchannel.getConnectivityState()] += 1; + if (subchannel.getConnectivityState() === ConnectivityState.READY) { + this.pickSubchannel(subchannel); + this.resetSubchannelList(); + return; + } + } + for (const [index, subchannel] of this.subchannels.entries()) { + const subchannelState = subchannel.getConnectivityState(); + if ( + subchannelState === ConnectivityState.IDLE || + subchannelState === ConnectivityState.CONNECTING + ) { + this.startConnecting(index); + if (this.currentPick === null) { + this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); + } + return; + } + } + // If the code reaches this point, every subchannel must be in TRANSIENT_FAILURE + if (this.currentPick === null) { + this.updateState( + ConnectivityState.TRANSIENT_FAILURE, + new UnavailablePicker() + ); + } + } + + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig + ): void { + // lbConfig has no useful information for pick first load balancing + /* To avoid unnecessary churn, we only do something with this address list + * if we're not currently trying to establish a connection, or if the new + * address list is different from the existing one */ + if ( + this.subchannels.length === 0 || + this.latestAddressList.length !== addressList.length || + !this.latestAddressList.every( + (value, index) => addressList[index] && subchannelAddressEqual(addressList[index], value) + ) + ) { + this.latestAddressList = addressList; + this.connectToAddressList(); + } + } + + exitIdle() { + if ( + this.currentState === ConnectivityState.IDLE || + this.triedAllSubchannels + ) { + this.channelControlHelper.requestReresolution(); + } + for (const subchannel of this.subchannels) { + subchannel.startConnecting(); + } + if (this.currentState === ConnectivityState.IDLE) { + if (this.latestAddressList.length > 0) { + this.connectToAddressList(); + } + } + } + + resetBackoff() { + /* The pick first load balancer does not have a connection backoff, so this + * does nothing */ + } + + destroy() { + this.resetSubchannelList(); + if (this.currentPick !== null) { + /* Unref can cause a state change, which can cause a change in the value + * of this.currentPick, so we hold a local reference to make sure that + * does not impact this function. */ + const currentPick = this.currentPick; + currentPick.unref(); + currentPick.removeConnectivityStateListener( + this.pickedSubchannelStateListener + ); + this.channelControlHelper.removeChannelzChild(currentPick.getChannelzRef()); + } + } + + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup(): void { + registerLoadBalancerType( + TYPE_NAME, + PickFirstLoadBalancer, + PickFirstLoadBalancingConfig + ); + registerDefaultLoadBalancerType(TYPE_NAME); +} diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts new file mode 100644 index 000000000..91c10b238 --- /dev/null +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -0,0 +1,257 @@ +/* + * Copyright 2019 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. + * + */ + +import { + LoadBalancer, + ChannelControlHelper, + LoadBalancingConfig, + registerLoadBalancerType, +} from './load-balancer'; +import { ConnectivityState } from './connectivity-state'; +import { + QueuePicker, + Picker, + PickArgs, + CompletePickResult, + PickResultType, + UnavailablePicker, +} from './picker'; +import { + SubchannelAddress, + subchannelAddressToString, +} from './subchannel-address'; +import * as logging from './logging'; +import { LogVerbosity } from './constants'; +import { ConnectivityStateListener, SubchannelInterface } from './subchannel-interface'; + +const TRACER_NAME = 'round_robin'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'round_robin'; + +class RoundRobinLoadBalancingConfig implements LoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + + constructor() {} + + toJsonObject(): object { + return { + [TYPE_NAME]: {}, + }; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static createFromJson(obj: any) { + return new RoundRobinLoadBalancingConfig(); + } +} + +class RoundRobinPicker implements Picker { + constructor( + private readonly subchannelList: SubchannelInterface[], + private nextIndex = 0 + ) {} + + pick(pickArgs: PickArgs): CompletePickResult { + const pickedSubchannel = this.subchannelList[this.nextIndex]; + this.nextIndex = (this.nextIndex + 1) % this.subchannelList.length; + return { + pickResultType: PickResultType.COMPLETE, + subchannel: pickedSubchannel, + status: null, + onCallStarted: null, + onCallEnded: null + }; + } + + /** + * Check what the next subchannel returned would be. Used by the load + * balancer implementation to preserve this part of the picker state if + * possible when a subchannel connects or disconnects. + */ + peekNextSubchannel(): SubchannelInterface { + return this.subchannelList[this.nextIndex]; + } +} + +interface ConnectivityStateCounts { + [ConnectivityState.CONNECTING]: number; + [ConnectivityState.IDLE]: number; + [ConnectivityState.READY]: number; + [ConnectivityState.SHUTDOWN]: number; + [ConnectivityState.TRANSIENT_FAILURE]: number; +} + +export class RoundRobinLoadBalancer implements LoadBalancer { + private subchannels: SubchannelInterface[] = []; + + private currentState: ConnectivityState = ConnectivityState.IDLE; + + private subchannelStateListener: ConnectivityStateListener; + + private subchannelStateCounts: ConnectivityStateCounts; + + private currentReadyPicker: RoundRobinPicker | null = null; + + constructor(private readonly channelControlHelper: ChannelControlHelper) { + this.subchannelStateCounts = { + [ConnectivityState.CONNECTING]: 0, + [ConnectivityState.IDLE]: 0, + [ConnectivityState.READY]: 0, + [ConnectivityState.SHUTDOWN]: 0, + [ConnectivityState.TRANSIENT_FAILURE]: 0, + }; + this.subchannelStateListener = ( + subchannel: SubchannelInterface, + previousState: ConnectivityState, + newState: ConnectivityState + ) => { + this.subchannelStateCounts[previousState] -= 1; + this.subchannelStateCounts[newState] += 1; + this.calculateAndUpdateState(); + + if ( + newState === ConnectivityState.TRANSIENT_FAILURE || + newState === ConnectivityState.IDLE + ) { + this.channelControlHelper.requestReresolution(); + subchannel.startConnecting(); + } + }; + } + + private calculateAndUpdateState() { + if (this.subchannelStateCounts[ConnectivityState.READY] > 0) { + const readySubchannels = this.subchannels.filter( + (subchannel) => + subchannel.getConnectivityState() === ConnectivityState.READY + ); + let index = 0; + if (this.currentReadyPicker !== null) { + index = readySubchannels.indexOf( + this.currentReadyPicker.peekNextSubchannel() + ); + if (index < 0) { + index = 0; + } + } + this.updateState( + ConnectivityState.READY, + new RoundRobinPicker(readySubchannels, index) + ); + } else if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) { + this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); + } else if ( + this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > 0 + ) { + this.updateState( + ConnectivityState.TRANSIENT_FAILURE, + new UnavailablePicker() + ); + } else { + this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); + } + } + + private updateState(newState: ConnectivityState, picker: Picker) { + trace( + ConnectivityState[this.currentState] + + ' -> ' + + ConnectivityState[newState] + ); + if (newState === ConnectivityState.READY) { + this.currentReadyPicker = picker as RoundRobinPicker; + } else { + this.currentReadyPicker = null; + } + this.currentState = newState; + this.channelControlHelper.updateState(newState, picker); + } + + private resetSubchannelList() { + for (const subchannel of this.subchannels) { + subchannel.removeConnectivityStateListener(this.subchannelStateListener); + subchannel.unref(); + this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); + } + this.subchannelStateCounts = { + [ConnectivityState.CONNECTING]: 0, + [ConnectivityState.IDLE]: 0, + [ConnectivityState.READY]: 0, + [ConnectivityState.SHUTDOWN]: 0, + [ConnectivityState.TRANSIENT_FAILURE]: 0, + }; + this.subchannels = []; + } + + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig + ): void { + this.resetSubchannelList(); + trace( + 'Connect to address list ' + + addressList.map((address) => subchannelAddressToString(address)) + ); + this.subchannels = addressList.map((address) => + this.channelControlHelper.createSubchannel(address, {}) + ); + for (const subchannel of this.subchannels) { + subchannel.ref(); + subchannel.addConnectivityStateListener(this.subchannelStateListener); + this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); + const subchannelState = subchannel.getConnectivityState(); + this.subchannelStateCounts[subchannelState] += 1; + if ( + subchannelState === ConnectivityState.IDLE || + subchannelState === ConnectivityState.TRANSIENT_FAILURE + ) { + subchannel.startConnecting(); + } + } + this.calculateAndUpdateState(); + } + + exitIdle(): void { + for (const subchannel of this.subchannels) { + subchannel.startConnecting(); + } + } + resetBackoff(): void { + /* The pick first load balancer does not have a connection backoff, so this + * does nothing */ + } + destroy(): void { + this.resetSubchannelList(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType( + TYPE_NAME, + RoundRobinLoadBalancer, + RoundRobinLoadBalancingConfig + ); +} diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts new file mode 100644 index 000000000..8d1c96981 --- /dev/null +++ b/packages/grpc-js/src/load-balancer.ts @@ -0,0 +1,218 @@ +/* + * Copyright 2019 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. + * + */ + +import { ChannelOptions } from './channel-options'; +import { SubchannelAddress } from './subchannel-address'; +import { ConnectivityState } from './connectivity-state'; +import { Picker } from './picker'; +import { ChannelRef, SubchannelRef } from './channelz'; +import { SubchannelInterface } from './subchannel-interface'; + +/** + * A collection of functions associated with a channel that a load balancer + * can call as necessary. + */ +export interface ChannelControlHelper { + /** + * Returns a subchannel connected to the specified address. + * @param subchannelAddress The address to connect to + * @param subchannelArgs Extra channel arguments specified by the load balancer + */ + createSubchannel( + subchannelAddress: SubchannelAddress, + subchannelArgs: ChannelOptions + ): SubchannelInterface; + /** + * Passes a new subchannel picker up to the channel. This is called if either + * the connectivity state changes or if a different picker is needed for any + * other reason. + * @param connectivityState New connectivity state + * @param picker New picker + */ + updateState(connectivityState: ConnectivityState, picker: Picker): void; + /** + * Request new data from the resolver. + */ + requestReresolution(): void; + addChannelzChild(child: ChannelRef | SubchannelRef): void; + removeChannelzChild(child: ChannelRef | SubchannelRef): void; +} + +/** + * Create a child ChannelControlHelper that overrides some methods of the + * parent while letting others pass through to the parent unmodified. This + * allows other code to create these children without needing to know about + * all of the methods to be passed through. + * @param parent + * @param overrides + */ +export function createChildChannelControlHelper(parent: ChannelControlHelper, overrides: Partial): ChannelControlHelper { + return { + createSubchannel: overrides.createSubchannel?.bind(overrides) ?? parent.createSubchannel.bind(parent), + updateState: overrides.updateState?.bind(overrides) ?? parent.updateState.bind(parent), + requestReresolution: overrides.requestReresolution?.bind(overrides) ?? parent.requestReresolution.bind(parent), + addChannelzChild: overrides.addChannelzChild?.bind(overrides) ?? parent.addChannelzChild.bind(parent), + removeChannelzChild: overrides.removeChannelzChild?.bind(overrides) ?? parent.removeChannelzChild.bind(parent) + }; +} + +/** + * Tracks one or more connected subchannels and determines which subchannel + * each request should use. + */ +export interface LoadBalancer { + /** + * Gives the load balancer a new list of addresses to start connecting to. + * The load balancer will start establishing connections with the new list, + * but will continue using any existing connections until the new connections + * are established + * @param addressList The new list of addresses to connect to + * @param lbConfig The load balancing config object from the service config, + * if one was provided + */ + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void; + /** + * If the load balancer is currently in the IDLE state, start connecting. + */ + exitIdle(): void; + /** + * If the load balancer is currently in the CONNECTING or TRANSIENT_FAILURE + * state, reset the current connection backoff timeout to its base value and + * transition to CONNECTING if in TRANSIENT_FAILURE. + */ + resetBackoff(): void; + /** + * The load balancer unrefs all of its subchannels and stops calling methods + * of its channel control helper. + */ + destroy(): void; + /** + * Get the type name for this load balancer type. Must be constant across an + * entire load balancer implementation class and must match the name that the + * balancer implementation class was registered with. + */ + getTypeName(): string; +} + +export interface LoadBalancerConstructor { + new (channelControlHelper: ChannelControlHelper): LoadBalancer; +} + +export interface LoadBalancingConfig { + getLoadBalancerName(): string; + toJsonObject(): object; +} + +export interface LoadBalancingConfigConstructor { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new (...args: any): LoadBalancingConfig; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createFromJson(obj: any): LoadBalancingConfig; +} + +const registeredLoadBalancerTypes: { + [name: string]: { + LoadBalancer: LoadBalancerConstructor; + LoadBalancingConfig: LoadBalancingConfigConstructor; + }; +} = {}; + +let defaultLoadBalancerType: string | null = null; + +export function registerLoadBalancerType( + typeName: string, + loadBalancerType: LoadBalancerConstructor, + loadBalancingConfigType: LoadBalancingConfigConstructor +) { + registeredLoadBalancerTypes[typeName] = { + LoadBalancer: loadBalancerType, + LoadBalancingConfig: loadBalancingConfigType, + }; +} + +export function registerDefaultLoadBalancerType(typeName: string) { + defaultLoadBalancerType = typeName; +} + +export function createLoadBalancer( + config: LoadBalancingConfig, + channelControlHelper: ChannelControlHelper +): LoadBalancer | null { + const typeName = config.getLoadBalancerName(); + if (typeName in registeredLoadBalancerTypes) { + return new registeredLoadBalancerTypes[typeName].LoadBalancer( + channelControlHelper + ); + } else { + return null; + } +} + +export function isLoadBalancerNameRegistered(typeName: string): boolean { + return typeName in registeredLoadBalancerTypes; +} + +export function getFirstUsableConfig( + configs: LoadBalancingConfig[], + fallbackTodefault?: true +): LoadBalancingConfig; +export function getFirstUsableConfig( + configs: LoadBalancingConfig[], + fallbackTodefault = false +): LoadBalancingConfig | null { + for (const config of configs) { + if (config.getLoadBalancerName() in registeredLoadBalancerTypes) { + return config; + } + } + if (fallbackTodefault) { + if (defaultLoadBalancerType) { + return new registeredLoadBalancerTypes[ + defaultLoadBalancerType + ]!.LoadBalancingConfig(); + } else { + return null; + } + } else { + return null; + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function validateLoadBalancingConfig(obj: any): LoadBalancingConfig { + if (!(obj !== null && typeof obj === 'object')) { + throw new Error('Load balancing config must be an object'); + } + const keys = Object.keys(obj); + if (keys.length !== 1) { + throw new Error( + 'Provided load balancing config has multiple conflicting entries' + ); + } + const typeName = keys[0]; + if (typeName in registeredLoadBalancerTypes) { + return registeredLoadBalancerTypes[ + typeName + ].LoadBalancingConfig.createFromJson(obj[typeName]); + } else { + throw new Error(`Unrecognized load balancing config name ${typeName}`); + } +} diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts new file mode 100644 index 000000000..e9144cb93 --- /dev/null +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -0,0 +1,281 @@ +/* + * Copyright 2022 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. + * + */ + +import { CallCredentials } from "./call-credentials"; +import { Call, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; +import { SubchannelCall } from "./subchannel-call"; +import { ConnectivityState } from "./connectivity-state"; +import { LogVerbosity, Status } from "./constants"; +import { Deadline, getDeadlineTimeoutString } from "./deadline"; +import { InternalChannel } from "./internal-channel"; +import { Metadata } from "./metadata"; +import { PickResultType } from "./picker"; +import { CallConfig } from "./resolver"; +import { splitHostPort } from "./uri-parser"; +import * as logging from './logging'; +import { restrictControlPlaneStatusCode } from "./control-plane-status"; +import * as http2 from 'http2'; + +const TRACER_NAME = 'load_balancing_call'; + +export type RpcProgress = 'NOT_STARTED' | 'DROP' | 'REFUSED' | 'PROCESSED'; + +export interface StatusObjectWithProgress extends StatusObject { + progress: RpcProgress; +} + +export interface LoadBalancingCallInterceptingListener extends InterceptingListener { + onReceiveStatus(status: StatusObjectWithProgress): void; +} + +export class LoadBalancingCall implements Call { + private child: SubchannelCall | null = null; + private readPending = false; + private pendingMessage: {context: MessageContext, message: Buffer} | null = null; + private pendingHalfClose = false; + private ended = false; + private serviceUrl: string; + private metadata: Metadata | null = null; + private listener: InterceptingListener | null = null; + private onCallEnded: ((statusCode: Status) => void) | null = null; + constructor( + private readonly channel: InternalChannel, + private readonly callConfig: CallConfig, + private readonly methodName: string, + private readonly host : string, + private readonly credentials: CallCredentials, + private readonly deadline: Deadline, + private readonly callNumber: number + ) { + const splitPath: string[] = this.methodName.split('/'); + let serviceName = ''; + /* The standard path format is "/{serviceName}/{methodName}", so if we split + * by '/', the first item should be empty and the second should be the + * service name */ + if (splitPath.length >= 2) { + serviceName = splitPath[1]; + } + const hostname = splitHostPort(this.host)?.host ?? 'localhost'; + /* Currently, call credentials are only allowed on HTTPS connections, so we + * can assume that the scheme is "https" */ + this.serviceUrl = `https://${hostname}/${serviceName}`; + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callNumber + '] ' + text + ); + } + + private outputStatus(status: StatusObject, progress: RpcProgress) { + if (!this.ended) { + this.ended = true; + this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"'); + const finalStatus = {...status, progress}; + this.listener?.onReceiveStatus(finalStatus); + this.onCallEnded?.(finalStatus.code); + } + } + + doPick() { + if (this.ended) { + return; + } + if (!this.metadata) { + throw new Error('doPick called before start'); + } + this.trace('Pick called') + const pickResult = this.channel.doPick(this.metadata, this.callConfig.pickInformation); + const subchannelString = pickResult.subchannel ? + '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() : + '' + pickResult.subchannel; + this.trace( + 'Pick result: ' + + PickResultType[pickResult.pickResultType] + + ' subchannel: ' + + subchannelString + + ' status: ' + + pickResult.status?.code + + ' ' + + pickResult.status?.details + ); + switch (pickResult.pickResultType) { + case PickResultType.COMPLETE: + this.credentials.generateMetadata({service_url: this.serviceUrl}).then( + (credsMetadata) => { + const finalMetadata = this.metadata!.clone(); + finalMetadata.merge(credsMetadata); + if (finalMetadata.get('authorization').length > 1) { + this.outputStatus( + { + code: Status.INTERNAL, + details: '"authorization" metadata cannot have multiple values', + metadata: new Metadata() + }, + 'PROCESSED' + ); + } + if (pickResult.subchannel!.getConnectivityState() !== ConnectivityState.READY) { + this.trace( + 'Picked subchannel ' + + subchannelString + + ' has state ' + + ConnectivityState[pickResult.subchannel!.getConnectivityState()] + + ' after getting credentials metadata. Retrying pick' + ); + this.doPick(); + return; + } + + if (this.deadline !== Infinity) { + finalMetadata.set('grpc-timeout', getDeadlineTimeoutString(this.deadline)); + } + try { + this.child = pickResult.subchannel!.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, { + onReceiveMetadata: metadata => { + this.trace('Received metadata'); + this.listener!.onReceiveMetadata(metadata); + }, + onReceiveMessage: message => { + this.trace('Received message'); + this.listener!.onReceiveMessage(message); + }, + onReceiveStatus: status => { + this.trace('Received status'); + if (status.rstCode === http2.constants.NGHTTP2_REFUSED_STREAM) { + this.outputStatus(status, 'REFUSED'); + } else { + this.outputStatus(status, 'PROCESSED'); + } + } + }); + } catch (error) { + this.trace( + 'Failed to start call on picked subchannel ' + + subchannelString + + ' with error ' + + (error as Error).message + ); + this.outputStatus( + { + code: Status.INTERNAL, + details: 'Failed to start HTTP/2 stream with error ' + (error as Error).message, + metadata: new Metadata() + }, + 'NOT_STARTED' + ); + return; + } + this.callConfig.onCommitted?.(); + pickResult.onCallStarted?.(); + this.onCallEnded = pickResult.onCallEnded; + this.trace('Created child call [' + this.child.getCallNumber() + ']'); + if (this.readPending) { + this.child.startRead(); + } + if (this.pendingMessage) { + this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message); + } + if (this.pendingHalfClose) { + this.child.halfClose(); + } + }, (error: Error & { code: number }) => { + // We assume the error code isn't 0 (Status.OK) + const {code, details} = restrictControlPlaneStatusCode( + typeof error.code === 'number' ? error.code : Status.UNKNOWN, + `Getting metadata from plugin failed with error: ${error.message}` + ) + this.outputStatus( + { + code: code, + details: details, + metadata: new Metadata() + }, + 'PROCESSED' + ); + } + ); + break; + case PickResultType.DROP: + const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); + setImmediate(() => { + this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'DROP'); + }); + break; + case PickResultType.TRANSIENT_FAILURE: + if (this.metadata.getOptions().waitForReady) { + this.channel.queueCallForPick(this); + } else { + const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); + setImmediate(() => { + this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'PROCESSED'); + }); + } + break; + case PickResultType.QUEUE: + this.channel.queueCallForPick(this); + } + } + + cancelWithStatus(status: Status, details: string): void { + this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); + this.child?.cancelWithStatus(status, details); + this.outputStatus({code: status, details: details, metadata: new Metadata()}, 'PROCESSED'); + } + getPeer(): string { + return this.child?.getPeer() ?? this.channel.getTarget(); + } + start(metadata: Metadata, listener: LoadBalancingCallInterceptingListener): void { + this.trace('start called'); + this.listener = listener; + this.metadata = metadata; + this.doPick(); + } + sendMessageWithContext(context: MessageContext, message: Buffer): void { + this.trace('write() called with message of length ' + message.length); + if (this.child) { + this.child.sendMessageWithContext(context, message); + } else { + this.pendingMessage = {context, message}; + } + } + startRead(): void { + this.trace('startRead called'); + if (this.child) { + this.child.startRead(); + } else { + this.readPending = true; + } + } + halfClose(): void { + this.trace('halfClose called'); + if (this.child) { + this.child.halfClose(); + } else { + this.pendingHalfClose = true; + } + } + setCredentials(credentials: CallCredentials): void { + throw new Error("Method not implemented."); + } + + getCallNumber(): number { + return this.callNumber; + } +} diff --git a/packages/grpc-js/src/logging.ts b/packages/grpc-js/src/logging.ts new file mode 100644 index 000000000..ec845c1a5 --- /dev/null +++ b/packages/grpc-js/src/logging.ts @@ -0,0 +1,119 @@ +/* + * Copyright 2019 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. + * + */ + +import { LogVerbosity } from './constants'; + +const DEFAULT_LOGGER: Partial = { + error: (message?: any, ...optionalParams: any[]) => { + console.error('E ' + message, ...optionalParams); + }, + info: (message?: any, ...optionalParams: any[]) => { + console.error('I ' + message, ...optionalParams); + }, + debug: (message?: any, ...optionalParams: any[]) => { + console.error('D ' + message, ...optionalParams); + }, +} + +let _logger: Partial = DEFAULT_LOGGER; +let _logVerbosity: LogVerbosity = LogVerbosity.ERROR; + +const verbosityString = + process.env.GRPC_NODE_VERBOSITY ?? process.env.GRPC_VERBOSITY ?? ''; + +switch (verbosityString.toUpperCase()) { + case 'DEBUG': + _logVerbosity = LogVerbosity.DEBUG; + break; + case 'INFO': + _logVerbosity = LogVerbosity.INFO; + break; + case 'ERROR': + _logVerbosity = LogVerbosity.ERROR; + break; + case 'NONE': + _logVerbosity = LogVerbosity.NONE; + break; + default: + // Ignore any other values +} + +export const getLogger = (): Partial => { + return _logger; +}; + +export const setLogger = (logger: Partial): void => { + _logger = logger; +}; + +export const setLoggerVerbosity = (verbosity: LogVerbosity): void => { + _logVerbosity = verbosity; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const log = (severity: LogVerbosity, ...args: any[]): void => { + let logFunction: typeof DEFAULT_LOGGER.error; + if (severity >= _logVerbosity) { + switch (severity) { + case LogVerbosity.DEBUG: + logFunction = _logger.debug; + break; + case LogVerbosity.INFO: + logFunction = _logger.info; + break; + case LogVerbosity.ERROR: + logFunction = _logger.error; + break; + } + /* Fall back to _logger.error when other methods are not available for + * compatiblity with older behavior that always logged to _logger.error */ + if (!logFunction) { + logFunction = _logger.error; + } + if (logFunction) { + logFunction.bind(_logger)(...args); + } + } +}; + +const tracersString = + process.env.GRPC_NODE_TRACE ?? process.env.GRPC_TRACE ?? ''; +const enabledTracers = new Set(); +const disabledTracers = new Set(); +for (const tracerName of tracersString.split(',')) { + if (tracerName.startsWith('-')) { + disabledTracers.add(tracerName.substring(1)); + } else { + enabledTracers.add(tracerName); + } +} +const allEnabled = enabledTracers.has('all'); + +export function trace( + severity: LogVerbosity, + tracer: string, + text: string +): void { + if (isTracerEnabled(tracer)) { + log(severity, new Date().toISOString() + ' | ' + tracer + ' | ' + text); + } +} + +export function isTracerEnabled(tracer: string): boolean { + return !disabledTracers.has(tracer) && + (allEnabled || enabledTracers.has(tracer)); +} diff --git a/packages/grpc-js/src/make-client.ts b/packages/grpc-js/src/make-client.ts new file mode 100644 index 000000000..7d08fee20 --- /dev/null +++ b/packages/grpc-js/src/make-client.ts @@ -0,0 +1,238 @@ +/* + * Copyright 2019 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. + * + */ + +import { ChannelCredentials } from './channel-credentials'; +import { ChannelOptions } from './channel-options'; +import { Client } from './client'; +import { UntypedServiceImplementation } from './server'; + +export interface Serialize { + (value: T): Buffer; +} + +export interface Deserialize { + (bytes: Buffer): T; +} + +export interface ClientMethodDefinition { + path: string; + requestStream: boolean; + responseStream: boolean; + requestSerialize: Serialize; + responseDeserialize: Deserialize; + originalName?: string; +} + +export interface ServerMethodDefinition { + path: string; + requestStream: boolean; + responseStream: boolean; + responseSerialize: Serialize; + requestDeserialize: Deserialize; + originalName?: string; +} + +export interface MethodDefinition + extends ClientMethodDefinition, + ServerMethodDefinition {} + +/* eslint-disable @typescript-eslint/no-explicit-any */ +export type ServiceDefinition< + ImplementationType = UntypedServiceImplementation +> = { + readonly [index in keyof ImplementationType]: MethodDefinition; +}; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +export interface ProtobufTypeDefinition { + format: string; + type: object; + fileDescriptorProtos: Buffer[]; +} + +export interface PackageDefinition { + [index: string]: ServiceDefinition | ProtobufTypeDefinition; +} + +/** + * Map with short names for each of the requester maker functions. Used in + * makeClientConstructor + * @private + */ +const requesterFuncs = { + unary: Client.prototype.makeUnaryRequest, + server_stream: Client.prototype.makeServerStreamRequest, + client_stream: Client.prototype.makeClientStreamRequest, + bidi: Client.prototype.makeBidiStreamRequest, +}; + +export interface ServiceClient extends Client { + [methodName: string]: Function; +} + +export interface ServiceClientConstructor { + new ( + address: string, + credentials: ChannelCredentials, + options?: Partial + ): ServiceClient; + service: ServiceDefinition; + serviceName: string; +} + +/** + * Returns true, if given key is included in the blacklisted + * keys. + * @param key key for check, string. + */ +function isPrototypePolluted(key: string): boolean { + return ['__proto__', 'prototype', 'constructor'].includes(key); +} + +/** + * Creates a constructor for a client with the given methods, as specified in + * the methods argument. The resulting class will have an instance method for + * each method in the service, which is a partial application of one of the + * [Client]{@link grpc.Client} request methods, depending on `requestSerialize` + * and `responseSerialize`, with the `method`, `serialize`, and `deserialize` + * arguments predefined. + * @param methods An object mapping method names to + * method attributes + * @param serviceName The fully qualified name of the service + * @param classOptions An options object. + * @return New client constructor, which is a subclass of + * {@link grpc.Client}, and has the same arguments as that constructor. + */ +export function makeClientConstructor( + methods: ServiceDefinition, + serviceName: string, + classOptions?: {} +): ServiceClientConstructor { + if (!classOptions) { + classOptions = {}; + } + + class ServiceClientImpl extends Client implements ServiceClient { + static service: ServiceDefinition; + static serviceName: string; + [methodName: string]: Function; + } + + Object.keys(methods).forEach((name) => { + if (isPrototypePolluted(name)) { + return; + } + const attrs = methods[name]; + let methodType: keyof typeof requesterFuncs; + // TODO(murgatroid99): Verify that we don't need this anymore + if (typeof name === 'string' && name.charAt(0) === '$') { + throw new Error('Method names cannot start with $'); + } + if (attrs.requestStream) { + if (attrs.responseStream) { + methodType = 'bidi'; + } else { + methodType = 'client_stream'; + } + } else { + if (attrs.responseStream) { + methodType = 'server_stream'; + } else { + methodType = 'unary'; + } + } + const serialize = attrs.requestSerialize; + const deserialize = attrs.responseDeserialize; + const methodFunc = partial( + requesterFuncs[methodType], + attrs.path, + serialize, + deserialize + ); + ServiceClientImpl.prototype[name] = methodFunc; + // Associate all provided attributes with the method + Object.assign(ServiceClientImpl.prototype[name], attrs); + if (attrs.originalName && !isPrototypePolluted(attrs.originalName)) { + ServiceClientImpl.prototype[attrs.originalName] = + ServiceClientImpl.prototype[name]; + } + }); + + ServiceClientImpl.service = methods; + ServiceClientImpl.serviceName = serviceName; + + return ServiceClientImpl; +} + +function partial( + fn: Function, + path: string, + serialize: Function, + deserialize: Function +): Function { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function (this: any, ...args: any[]) { + return fn.call(this, path, serialize, deserialize, ...args); + }; +} + +export interface GrpcObject { + [index: string]: + | GrpcObject + | ServiceClientConstructor + | ProtobufTypeDefinition; +} + +function isProtobufTypeDefinition( + obj: ServiceDefinition | ProtobufTypeDefinition +): obj is ProtobufTypeDefinition { + return 'format' in obj; +} + +/** + * Load a gRPC package definition as a gRPC object hierarchy. + * @param packageDef The package definition object. + * @return The resulting gRPC object. + */ +export function loadPackageDefinition( + packageDef: PackageDefinition +): GrpcObject { + const result: GrpcObject = {}; + for (const serviceFqn in packageDef) { + if (Object.prototype.hasOwnProperty.call(packageDef, serviceFqn)) { + const service = packageDef[serviceFqn]; + const nameComponents = serviceFqn.split('.'); + if (nameComponents.some((comp: string) => isPrototypePolluted(comp))) { + continue; + } + const serviceName = nameComponents[nameComponents.length - 1]; + let current = result; + for (const packageName of nameComponents.slice(0, -1)) { + if (!current[packageName]) { + current[packageName] = {}; + } + current = current[packageName] as GrpcObject; + } + if (isProtobufTypeDefinition(service)) { + current[serviceName] = service; + } else { + current[serviceName] = makeClientConstructor(service, serviceName, {}); + } + } + } + return result; +} diff --git a/packages/grpc-js/src/metadata.ts b/packages/grpc-js/src/metadata.ts new file mode 100644 index 000000000..bfef51b0d --- /dev/null +++ b/packages/grpc-js/src/metadata.ts @@ -0,0 +1,295 @@ +/* + * Copyright 2019 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. + * + */ + +import * as http2 from 'http2'; +import { log } from './logging'; +import { LogVerbosity } from './constants'; +import { getErrorMessage } from './error'; +const LEGAL_KEY_REGEX = /^[0-9a-z_.-]+$/; +const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/; + +export type MetadataValue = string | Buffer; +export type MetadataObject = Map; + +function isLegalKey(key: string): boolean { + return LEGAL_KEY_REGEX.test(key); +} + +function isLegalNonBinaryValue(value: string): boolean { + return LEGAL_NON_BINARY_VALUE_REGEX.test(value); +} + +function isBinaryKey(key: string): boolean { + return key.endsWith('-bin'); +} + +function isCustomMetadata(key: string): boolean { + return !key.startsWith('grpc-'); +} + +function normalizeKey(key: string): string { + return key.toLowerCase(); +} + +function validate(key: string, value?: MetadataValue): void { + if (!isLegalKey(key)) { + throw new Error('Metadata key "' + key + '" contains illegal characters'); + } + + if (value !== null && value !== undefined) { + if (isBinaryKey(key)) { + if (!Buffer.isBuffer(value)) { + throw new Error("keys that end with '-bin' must have Buffer values"); + } + } else { + if (Buffer.isBuffer(value)) { + throw new Error( + "keys that don't end with '-bin' must have String values" + ); + } + if (!isLegalNonBinaryValue(value)) { + throw new Error( + 'Metadata string value "' + value + '" contains illegal characters' + ); + } + } + } +} + +export interface MetadataOptions { + /* Signal that the request is idempotent. Defaults to false */ + idempotentRequest?: boolean; + /* Signal that the call should not return UNAVAILABLE before it has + * started. Defaults to false. */ + waitForReady?: boolean; + /* Signal that the call is cacheable. GRPC is free to use GET verb. + * Defaults to false */ + cacheableRequest?: boolean; + /* Signal that the initial metadata should be corked. Defaults to false. */ + corked?: boolean; +} + +/** + * A class for storing metadata. Keys are normalized to lowercase ASCII. + */ +export class Metadata { + protected internalRepr: MetadataObject = new Map(); + private options: MetadataOptions; + + constructor(options: MetadataOptions = {}) { + this.options = options; + } + + /** + * Sets the given value for the given key by replacing any other values + * associated with that key. Normalizes the key. + * @param key The key to whose value should be set. + * @param value The value to set. Must be a buffer if and only + * if the normalized key ends with '-bin'. + */ + set(key: string, value: MetadataValue): void { + key = normalizeKey(key); + validate(key, value); + this.internalRepr.set(key, [value]); + } + + /** + * Adds the given value for the given key by appending to a list of previous + * values associated with that key. Normalizes the key. + * @param key The key for which a new value should be appended. + * @param value The value to add. Must be a buffer if and only + * if the normalized key ends with '-bin'. + */ + add(key: string, value: MetadataValue): void { + key = normalizeKey(key); + validate(key, value); + + const existingValue: MetadataValue[] | undefined = this.internalRepr.get(key); + + if (existingValue === undefined) { + this.internalRepr.set(key, [value]); + } else { + existingValue.push(value); + } + } + + /** + * Removes the given key and any associated values. Normalizes the key. + * @param key The key whose values should be removed. + */ + remove(key: string): void { + key = normalizeKey(key); + // validate(key); + this.internalRepr.delete(key); + } + + /** + * Gets a list of all values associated with the key. Normalizes the key. + * @param key The key whose value should be retrieved. + * @return A list of values associated with the given key. + */ + get(key: string): MetadataValue[] { + key = normalizeKey(key); + // validate(key); + return this.internalRepr.get(key) || []; + } + + /** + * Gets a plain object mapping each key to the first value associated with it. + * This reflects the most common way that people will want to see metadata. + * @return A key/value mapping of the metadata. + */ + getMap(): { [key: string]: MetadataValue } { + const result: { [key: string]: MetadataValue } = {}; + + for (const [key, values] of this.internalRepr) { + if (values.length > 0) { + const v = values[0]; + result[key] = Buffer.isBuffer(v) ? Buffer.from(v) : v; + } + } + return result; + } + + /** + * Clones the metadata object. + * @return The newly cloned object. + */ + clone(): Metadata { + const newMetadata = new Metadata(this.options); + const newInternalRepr = newMetadata.internalRepr; + + for (const [key, value] of this.internalRepr) { + const clonedValue: MetadataValue[] = value.map((v) => { + if (Buffer.isBuffer(v)) { + return Buffer.from(v); + } else { + return v; + } + }); + + newInternalRepr.set(key, clonedValue); + } + + return newMetadata; + } + + /** + * Merges all key-value pairs from a given Metadata object into this one. + * If both this object and the given object have values in the same key, + * values from the other Metadata object will be appended to this object's + * values. + * @param other A Metadata object. + */ + merge(other: Metadata): void { + for (const [key, values] of other.internalRepr) { + const mergedValue: MetadataValue[] = ( + this.internalRepr.get(key) || [] + ).concat(values); + + this.internalRepr.set(key, mergedValue); + } + } + + setOptions(options: MetadataOptions) { + this.options = options; + } + + getOptions(): MetadataOptions { + return this.options; + } + + /** + * Creates an OutgoingHttpHeaders object that can be used with the http2 API. + */ + toHttp2Headers(): http2.OutgoingHttpHeaders { + // NOTE: Node <8.9 formats http2 headers incorrectly. + const result: http2.OutgoingHttpHeaders = {}; + + for (const [key, values] of this.internalRepr) { + // We assume that the user's interaction with this object is limited to + // through its public API (i.e. keys and values are already validated). + result[key] = values.map(bufToString); + } + + return result; + } + + /** + * This modifies the behavior of JSON.stringify to show an object + * representation of the metadata map. + */ + toJSON() { + const result: { [key: string]: MetadataValue[] } = {}; + for (const [key, values] of this.internalRepr) { + result[key] = values; + } + return result; + } + + /** + * Returns a new Metadata object based fields in a given IncomingHttpHeaders + * object. + * @param headers An IncomingHttpHeaders object. + */ + static fromHttp2Headers(headers: http2.IncomingHttpHeaders): Metadata { + const result = new Metadata(); + for (const key of Object.keys(headers)) { + // Reserved headers (beginning with `:`) are not valid keys. + if (key.charAt(0) === ':') { + continue; + } + + const values = headers[key]; + + try { + if (isBinaryKey(key)) { + if (Array.isArray(values)) { + values.forEach((value) => { + result.add(key, Buffer.from(value, 'base64')); + }); + } else if (values !== undefined) { + if (isCustomMetadata(key)) { + values.split(',').forEach((v) => { + result.add(key, Buffer.from(v.trim(), 'base64')); + }); + } else { + result.add(key, Buffer.from(values, 'base64')); + } + } + } else { + if (Array.isArray(values)) { + values.forEach((value) => { + result.add(key, value); + }); + } else if (values !== undefined) { + result.add(key, values); + } + } + } catch (error) { + const message = `Failed to add metadata entry ${key}: ${values}. ${getErrorMessage(error)}. For more information see https://github.com/grpc/grpc-node/issues/1173`; + log(LogVerbosity.ERROR, message); + } + } + + return result; + } +} + +const bufToString = (val: string | Buffer): string => { + return Buffer.isBuffer(val) ? val.toString('base64') : val +}; diff --git a/packages/grpc-js/src/object-stream.ts b/packages/grpc-js/src/object-stream.ts new file mode 100644 index 000000000..9289b9d84 --- /dev/null +++ b/packages/grpc-js/src/object-stream.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2019 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. + * + */ + +import { Readable, Writable } from 'stream'; +import { EmitterAugmentation1 } from './events'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export type WriteCallback = (error: Error | null | undefined) => void; + +export interface IntermediateObjectReadable extends Readable { + read(size?: number): any & T; +} + +export type ObjectReadable = { + read(size?: number): T; +} & EmitterAugmentation1<'data', T> & + IntermediateObjectReadable; + +export interface IntermediateObjectWritable extends Writable { + _write(chunk: any & T, encoding: string, callback: Function): void; + write(chunk: any & T, cb?: WriteCallback): boolean; + write(chunk: any & T, encoding?: any, cb?: WriteCallback): boolean; + setDefaultEncoding(encoding: string): this; + end(): ReturnType extends Writable ? this : void; + end(chunk: any & T, cb?: Function): ReturnType extends Writable ? this : void; + end(chunk: any & T, encoding?: any, cb?: Function): ReturnType extends Writable ? this : void; +} + +export interface ObjectWritable extends IntermediateObjectWritable { + _write(chunk: T, encoding: string, callback: Function): void; + write(chunk: T, cb?: Function): boolean; + write(chunk: T, encoding?: any, cb?: Function): boolean; + setDefaultEncoding(encoding: string): this; + end(): ReturnType extends Writable ? this : void; + end(chunk: T, cb?: Function): ReturnType extends Writable ? this : void; + end(chunk: T, encoding?: any, cb?: Function): ReturnType extends Writable ? this : void; +} diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts new file mode 100644 index 000000000..162596ef5 --- /dev/null +++ b/packages/grpc-js/src/picker.ts @@ -0,0 +1,149 @@ +/* + * Copyright 2019 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. + * + */ + +import { StatusObject } from './call-interface'; +import { Metadata } from './metadata'; +import { Status } from './constants'; +import { LoadBalancer } from './load-balancer'; +import { SubchannelInterface } from './subchannel-interface'; + +export enum PickResultType { + COMPLETE, + QUEUE, + TRANSIENT_FAILURE, + DROP, +} + +export interface PickResult { + pickResultType: PickResultType; + /** + * The subchannel to use as the transport for the call. Only meaningful if + * `pickResultType` is COMPLETE. If null, indicates that the call should be + * dropped. + */ + subchannel: SubchannelInterface | null; + /** + * The status object to end the call with. Populated if and only if + * `pickResultType` is TRANSIENT_FAILURE. + */ + status: StatusObject | null; + onCallStarted: (() => void) | null; + onCallEnded: ((statusCode: Status) => void) | null; +} + +export interface CompletePickResult extends PickResult { + pickResultType: PickResultType.COMPLETE; + subchannel: SubchannelInterface | null; + status: null; + onCallStarted: (() => void) | null; + onCallEnded: ((statusCode: Status) => void) | null; +} + +export interface QueuePickResult extends PickResult { + pickResultType: PickResultType.QUEUE; + subchannel: null; + status: null; + onCallStarted: null; + onCallEnded: null; +} + +export interface TransientFailurePickResult extends PickResult { + pickResultType: PickResultType.TRANSIENT_FAILURE; + subchannel: null; + status: StatusObject; + onCallStarted: null; + onCallEnded: null; +} + +export interface DropCallPickResult extends PickResult { + pickResultType: PickResultType.DROP; + subchannel: null; + status: StatusObject; + onCallStarted: null; + onCallEnded: null; +} + +export interface PickArgs { + metadata: Metadata; + extraPickInfo: { [key: string]: string }; +} + +/** + * A proxy object representing the momentary state of a load balancer. Picks + * subchannels or returns other information based on that state. Should be + * replaced every time the load balancer changes state. + */ +export interface Picker { + pick(pickArgs: PickArgs): PickResult; +} + +/** + * A standard picker representing a load balancer in the TRANSIENT_FAILURE + * state. Always responds to every pick request with an UNAVAILABLE status. + */ +export class UnavailablePicker implements Picker { + private status: StatusObject; + constructor(status?: StatusObject) { + if (status !== undefined) { + this.status = status; + } else { + this.status = { + code: Status.UNAVAILABLE, + details: 'No connection established', + metadata: new Metadata(), + }; + } + } + pick(pickArgs: PickArgs): TransientFailurePickResult { + return { + pickResultType: PickResultType.TRANSIENT_FAILURE, + subchannel: null, + status: this.status, + onCallStarted: null, + onCallEnded: null + }; + } +} + +/** + * A standard picker representing a load balancer in the IDLE or CONNECTING + * state. Always responds to every pick request with a QUEUE pick result + * indicating that the pick should be tried again with the next `Picker`. Also + * reports back to the load balancer that a connection should be established + * once any pick is attempted. + */ +export class QueuePicker { + private calledExitIdle = false; + // Constructed with a load balancer. Calls exitIdle on it the first time pick is called + constructor(private loadBalancer: LoadBalancer) {} + + pick(pickArgs: PickArgs): QueuePickResult { + if (!this.calledExitIdle) { + process.nextTick(() => { + this.loadBalancer.exitIdle(); + }); + this.calledExitIdle = true; + } + return { + pickResultType: PickResultType.QUEUE, + subchannel: null, + status: null, + onCallStarted: null, + onCallEnded: null + }; + } +} diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts new file mode 100644 index 000000000..775a25c36 --- /dev/null +++ b/packages/grpc-js/src/resolver-dns.ts @@ -0,0 +1,378 @@ +/* + * Copyright 2019 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. + */ + +import { + Resolver, + ResolverListener, + registerResolver, + registerDefaultScheme, +} from './resolver'; +import * as dns from 'dns'; +import * as util from 'util'; +import { extractAndSelectServiceConfig, ServiceConfig } from './service-config'; +import { Status } from './constants'; +import { StatusObject } from './call-interface'; +import { Metadata } from './metadata'; +import * as logging from './logging'; +import { LogVerbosity } from './constants'; +import { SubchannelAddress, TcpSubchannelAddress } from './subchannel-address'; +import { GrpcUri, uriToString, splitHostPort } from './uri-parser'; +import { isIPv6, isIPv4 } from 'net'; +import { ChannelOptions } from './channel-options'; +import { BackoffOptions, BackoffTimeout } from './backoff-timeout'; + +const TRACER_NAME = 'dns_resolver'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +/** + * The default TCP port to connect to if not explicitly specified in the target. + */ +const DEFAULT_PORT = 443; + +const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000; + +const resolveTxtPromise = util.promisify(dns.resolveTxt); +const dnsLookupPromise = util.promisify(dns.lookup); + +/** + * Merge any number of arrays into a single alternating array + * @param arrays + */ +function mergeArrays(...arrays: T[][]): T[] { + const result: T[] = []; + for ( + let i = 0; + i < + Math.max.apply( + null, + arrays.map((array) => array.length) + ); + i++ + ) { + for (const array of arrays) { + if (i < array.length) { + result.push(array[i]); + } + } + } + return result; +} + +/** + * Resolver implementation that handles DNS names and IP addresses. + */ +class DnsResolver implements Resolver { + private readonly ipResult: SubchannelAddress[] | null; + private readonly dnsHostname: string | null; + private readonly port: number | null; + /** + * Minimum time between resolutions, measured as the time between starting + * successive resolution requests. Only applies to successful resolutions. + * Failures are handled by the backoff timer. + */ + private readonly minTimeBetweenResolutionsMs: number; + private pendingLookupPromise: Promise | null = null; + private pendingTxtPromise: Promise | null = null; + private latestLookupResult: TcpSubchannelAddress[] | null = null; + private latestServiceConfig: ServiceConfig | null = null; + private latestServiceConfigError: StatusObject | null = null; + private percentage: number; + private defaultResolutionError: StatusObject; + private backoff: BackoffTimeout; + private continueResolving = false; + private nextResolutionTimer: NodeJS.Timeout; + private isNextResolutionTimerRunning = false; + private isServiceConfigEnabled = true; + constructor( + private target: GrpcUri, + private listener: ResolverListener, + channelOptions: ChannelOptions + ) { + trace('Resolver constructed for target ' + uriToString(target)); + const hostPort = splitHostPort(target.path); + if (hostPort === null) { + this.ipResult = null; + this.dnsHostname = null; + this.port = null; + } else { + if (isIPv4(hostPort.host) || isIPv6(hostPort.host)) { + this.ipResult = [ + { + host: hostPort.host, + port: hostPort.port ?? DEFAULT_PORT, + }, + ]; + this.dnsHostname = null; + this.port = null; + } else { + this.ipResult = null; + this.dnsHostname = hostPort.host; + this.port = hostPort.port ?? DEFAULT_PORT; + } + } + this.percentage = Math.random() * 100; + + if (channelOptions['grpc.service_config_disable_resolution'] === 1) { + this.isServiceConfigEnabled = false; + } + + this.defaultResolutionError = { + code: Status.UNAVAILABLE, + details: `Name resolution failed for target ${uriToString(this.target)}`, + metadata: new Metadata(), + }; + + const backoffOptions: BackoffOptions = { + initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'], + maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'], + }; + + this.backoff = new BackoffTimeout(() => { + if (this.continueResolving) { + this.startResolutionWithBackoff(); + } + }, backoffOptions); + this.backoff.unref(); + + this.minTimeBetweenResolutionsMs = channelOptions['grpc.dns_min_time_between_resolutions_ms'] ?? DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS; + this.nextResolutionTimer = setTimeout(() => {}, 0); + clearTimeout(this.nextResolutionTimer); + } + + /** + * If the target is an IP address, just provide that address as a result. + * Otherwise, initiate A, AAAA, and TXT lookups + */ + private startResolution() { + if (this.ipResult !== null) { + trace('Returning IP address for target ' + uriToString(this.target)); + setImmediate(() => { + this.listener.onSuccessfulResolution( + this.ipResult!, + null, + null, + null, + {} + ); + }); + this.backoff.stop(); + this.backoff.reset(); + return; + } + if (this.dnsHostname === null) { + trace('Failed to parse DNS address ' + uriToString(this.target)); + setImmediate(() => { + this.listener.onError({ + code: Status.UNAVAILABLE, + details: `Failed to parse DNS address ${uriToString(this.target)}`, + metadata: new Metadata(), + }); + }); + this.stopNextResolutionTimer(); + } else { + if (this.pendingLookupPromise !== null) { + return; + } + trace('Looking up DNS hostname ' + this.dnsHostname); + /* We clear out latestLookupResult here to ensure that it contains the + * latest result since the last time we started resolving. That way, the + * TXT resolution handler can use it, but only if it finishes second. We + * don't clear out any previous service config results because it's + * better to use a service config that's slightly out of date than to + * revert to an effectively blank one. */ + this.latestLookupResult = null; + const hostname: string = this.dnsHostname; + /* We lookup both address families here and then split them up later + * because when looking up a single family, dns.lookup outputs an error + * if the name exists but there are no records for that family, and that + * error is indistinguishable from other kinds of errors */ + this.pendingLookupPromise = dnsLookupPromise(hostname, { all: true }); + this.pendingLookupPromise.then( + (addressList) => { + this.pendingLookupPromise = null; + this.backoff.reset(); + this.backoff.stop(); + const ip4Addresses: dns.LookupAddress[] = addressList.filter( + (addr) => addr.family === 4 + ); + const ip6Addresses: dns.LookupAddress[] = addressList.filter( + (addr) => addr.family === 6 + ); + this.latestLookupResult = mergeArrays( + ip6Addresses, + ip4Addresses + ).map((addr) => ({ host: addr.address, port: +this.port! })); + const allAddressesString: string = + '[' + + this.latestLookupResult + .map((addr) => addr.host + ':' + addr.port) + .join(',') + + ']'; + trace( + 'Resolved addresses for target ' + + uriToString(this.target) + + ': ' + + allAddressesString + ); + if (this.latestLookupResult.length === 0) { + this.listener.onError(this.defaultResolutionError); + return; + } + /* If the TXT lookup has not yet finished, both of the last two + * arguments will be null, which is the equivalent of getting an + * empty TXT response. When the TXT lookup does finish, its handler + * can update the service config by using the same address list */ + this.listener.onSuccessfulResolution( + this.latestLookupResult, + this.latestServiceConfig, + this.latestServiceConfigError, + null, + {} + ); + }, + (err) => { + trace( + 'Resolution error for target ' + + uriToString(this.target) + + ': ' + + (err as Error).message + ); + this.pendingLookupPromise = null; + this.stopNextResolutionTimer(); + this.listener.onError(this.defaultResolutionError); + } + ); + /* If there already is a still-pending TXT resolution, we can just use + * that result when it comes in */ + if (this.isServiceConfigEnabled && this.pendingTxtPromise === null) { + /* We handle the TXT query promise differently than the others because + * the name resolution attempt as a whole is a success even if the TXT + * lookup fails */ + this.pendingTxtPromise = resolveTxtPromise(hostname); + this.pendingTxtPromise.then( + (txtRecord) => { + this.pendingTxtPromise = null; + try { + this.latestServiceConfig = extractAndSelectServiceConfig( + txtRecord, + this.percentage + ); + } catch (err) { + this.latestServiceConfigError = { + code: Status.UNAVAILABLE, + details: `Parsing service config failed with error ${(err as Error).message}`, + metadata: new Metadata(), + }; + } + if (this.latestLookupResult !== null) { + /* We rely here on the assumption that calling this function with + * identical parameters will be essentialy idempotent, and calling + * it with the same address list and a different service config + * should result in a fast and seamless switchover. */ + this.listener.onSuccessfulResolution( + this.latestLookupResult, + this.latestServiceConfig, + this.latestServiceConfigError, + null, + {} + ); + } + }, + (err) => { + /* If TXT lookup fails we should do nothing, which means that we + * continue to use the result of the most recent successful lookup, + * or the default null config object if there has never been a + * successful lookup. We do not set the latestServiceConfigError + * here because that is specifically used for response validation + * errors. We still need to handle this error so that it does not + * bubble up as an unhandled promise rejection. */ + } + ); + } + } + } + + private startNextResolutionTimer() { + clearTimeout(this.nextResolutionTimer); + this.nextResolutionTimer = setTimeout(() => { + this.stopNextResolutionTimer(); + if (this.continueResolving) { + this.startResolutionWithBackoff(); + } + }, this.minTimeBetweenResolutionsMs).unref?.(); + this.isNextResolutionTimerRunning = true; + } + + private stopNextResolutionTimer() { + clearTimeout(this.nextResolutionTimer); + this.isNextResolutionTimerRunning = false; + } + + private startResolutionWithBackoff() { + if (this.pendingLookupPromise === null) { + this.continueResolving = false; + this.startResolution(); + this.backoff.runOnce(); + this.startNextResolutionTimer(); + } + } + + updateResolution() { + /* If there is a pending lookup, just let it finish. Otherwise, if the + * nextResolutionTimer or backoff timer is running, set the + * continueResolving flag to resolve when whichever of those timers + * fires. Otherwise, start resolving immediately. */ + if (this.pendingLookupPromise === null) { + if (this.isNextResolutionTimerRunning || this.backoff.isRunning()) { + this.continueResolving = true; + } else { + this.startResolutionWithBackoff(); + } + } + } + + destroy() { + this.continueResolving = false; + this.backoff.stop(); + this.stopNextResolutionTimer(); + } + + /** + * Get the default authority for the given target. For IP targets, that is + * the IP address. For DNS targets, it is the hostname. + * @param target + */ + static getDefaultAuthority(target: GrpcUri): string { + return target.path; + } +} + +/** + * Set up the DNS resolver class by registering it as the handler for the + * "dns:" prefix and as the default resolver. + */ +export function setup(): void { + registerResolver('dns', DnsResolver); + registerDefaultScheme('dns'); +} + +export interface DnsUrl { + host: string; + port?: string; +} diff --git a/packages/grpc-js/src/resolver-ip.ts b/packages/grpc-js/src/resolver-ip.ts new file mode 100644 index 000000000..0704131e1 --- /dev/null +++ b/packages/grpc-js/src/resolver-ip.ts @@ -0,0 +1,116 @@ +/* + * Copyright 2021 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. + */ + +import { isIPv4, isIPv6 } from 'net'; +import { StatusObject } from './call-interface'; +import { ChannelOptions } from './channel-options'; +import { LogVerbosity, Status } from './constants'; +import { Metadata } from './metadata'; +import { registerResolver, Resolver, ResolverListener } from './resolver'; +import { SubchannelAddress } from './subchannel-address'; +import { GrpcUri, splitHostPort, uriToString } from './uri-parser'; +import * as logging from './logging'; + +const TRACER_NAME = 'ip_resolver'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +const IPV4_SCHEME = 'ipv4'; +const IPV6_SCHEME = 'ipv6'; + +/** + * The default TCP port to connect to if not explicitly specified in the target. + */ +const DEFAULT_PORT = 443; + +class IpResolver implements Resolver { + private addresses: SubchannelAddress[] = []; + private error: StatusObject | null = null; + constructor( + target: GrpcUri, + private listener: ResolverListener, + channelOptions: ChannelOptions + ) { + trace('Resolver constructed for target ' + uriToString(target)); + const addresses: SubchannelAddress[] = []; + if (!(target.scheme === IPV4_SCHEME || target.scheme === IPV6_SCHEME)) { + this.error = { + code: Status.UNAVAILABLE, + details: `Unrecognized scheme ${target.scheme} in IP resolver`, + metadata: new Metadata(), + }; + return; + } + const pathList = target.path.split(','); + for (const path of pathList) { + const hostPort = splitHostPort(path); + if (hostPort === null) { + this.error = { + code: Status.UNAVAILABLE, + details: `Failed to parse ${target.scheme} address ${path}`, + metadata: new Metadata(), + }; + return; + } + if ( + (target.scheme === IPV4_SCHEME && !isIPv4(hostPort.host)) || + (target.scheme === IPV6_SCHEME && !isIPv6(hostPort.host)) + ) { + this.error = { + code: Status.UNAVAILABLE, + details: `Failed to parse ${target.scheme} address ${path}`, + metadata: new Metadata(), + }; + return; + } + addresses.push({ + host: hostPort.host, + port: hostPort.port ?? DEFAULT_PORT, + }); + } + this.addresses = addresses; + trace('Parsed ' + target.scheme + ' address list ' + this.addresses); + } + updateResolution(): void { + process.nextTick(() => { + if (this.error) { + this.listener.onError(this.error); + } else { + this.listener.onSuccessfulResolution( + this.addresses, + null, + null, + null, + {} + ); + } + }); + } + destroy(): void { + // This resolver owns no resources, so we do nothing here. + } + + static getDefaultAuthority(target: GrpcUri): string { + return target.path.split(',')[0]; + } +} + +export function setup() { + registerResolver(IPV4_SCHEME, IpResolver); + registerResolver(IPV6_SCHEME, IpResolver); +} diff --git a/packages/grpc-js/src/resolver-uds.ts b/packages/grpc-js/src/resolver-uds.ts new file mode 100644 index 000000000..24095ec29 --- /dev/null +++ b/packages/grpc-js/src/resolver-uds.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2019 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. + */ + +import { Resolver, ResolverListener, registerResolver } from './resolver'; +import { SubchannelAddress } from './subchannel-address'; +import { GrpcUri } from './uri-parser'; +import { ChannelOptions } from './channel-options'; + +class UdsResolver implements Resolver { + private addresses: SubchannelAddress[] = []; + constructor( + target: GrpcUri, + private listener: ResolverListener, + channelOptions: ChannelOptions + ) { + let path: string; + if (target.authority === '') { + path = '/' + target.path; + } else { + path = target.path; + } + this.addresses = [{ path }]; + } + updateResolution(): void { + process.nextTick( + this.listener.onSuccessfulResolution, + this.addresses, + null, + null, + null, + {} + ); + } + + destroy() { + // This resolver owns no resources, so we do nothing here. + } + + static getDefaultAuthority(target: GrpcUri): string { + return 'localhost'; + } +} + +export function setup() { + registerResolver('unix', UdsResolver); +} diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts new file mode 100644 index 000000000..770004487 --- /dev/null +++ b/packages/grpc-js/src/resolver.ts @@ -0,0 +1,177 @@ +/* + * Copyright 2019 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. + * + */ + +import { MethodConfig, ServiceConfig } from './service-config'; +import { StatusObject } from './call-interface'; +import { SubchannelAddress } from './subchannel-address'; +import { GrpcUri, uriToString } from './uri-parser'; +import { ChannelOptions } from './channel-options'; +import { Metadata } from './metadata'; +import { Status } from './constants'; +import { Filter, FilterFactory } from './filter'; + +export interface CallConfig { + methodConfig: MethodConfig; + onCommitted?: () => void; + pickInformation: { [key: string]: string }; + status: Status; + dynamicFilterFactories: FilterFactory[]; +} + +/** + * Selects a configuration for a method given the name and metadata. Defined in + * https://github.com/grpc/proposal/blob/master/A31-xds-timeout-support-and-config-selector.md#new-functionality-in-grpc + */ +export interface ConfigSelector { + (methodName: string, metadata: Metadata): CallConfig; +} + +/** + * A listener object passed to the resolver's constructor that provides name + * resolution updates back to the resolver's owner. + */ +export interface ResolverListener { + /** + * Called whenever the resolver has new name resolution results to report + * @param addressList The new list of backend addresses + * @param serviceConfig The new service configuration corresponding to the + * `addressList`. Will be `null` if no service configuration was + * retrieved or if the service configuration was invalid + * @param serviceConfigError If non-`null`, indicates that the retrieved + * service configuration was invalid + */ + onSuccessfulResolution( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null, + configSelector: ConfigSelector | null, + attributes: { [key: string]: unknown } + ): void; + /** + * Called whenever a name resolution attempt fails. + * @param error Describes how resolution failed + */ + onError(error: StatusObject): void; +} + +/** + * A resolver class that handles one or more of the name syntax schemes defined + * in the [gRPC Name Resolution document](https://github.com/grpc/grpc/blob/master/doc/naming.md) + */ +export interface Resolver { + /** + * Indicates that the caller wants new name resolution data. Calling this + * function may eventually result in calling one of the `ResolverListener` + * functions, but that is not guaranteed. Those functions will never be + * called synchronously with the constructor or updateResolution. + */ + updateResolution(): void; + + /** + * Destroy the resolver. Should be called when the owning channel shuts down. + */ + destroy(): void; +} + +export interface ResolverConstructor { + new ( + target: GrpcUri, + listener: ResolverListener, + channelOptions: ChannelOptions + ): Resolver; + /** + * Get the default authority for a target. This loosely corresponds to that + * target's hostname. Throws an error if this resolver class cannot parse the + * `target`. + * @param target + */ + getDefaultAuthority(target: GrpcUri): string; +} + +const registeredResolvers: { [scheme: string]: ResolverConstructor } = {}; +let defaultScheme: string | null = null; + +/** + * Register a resolver class to handle target names prefixed with the `prefix` + * string. This prefix should correspond to a URI scheme name listed in the + * [gRPC Name Resolution document](https://github.com/grpc/grpc/blob/master/doc/naming.md) + * @param prefix + * @param resolverClass + */ +export function registerResolver( + scheme: string, + resolverClass: ResolverConstructor +) { + registeredResolvers[scheme] = resolverClass; +} + +/** + * Register a default resolver to handle target names that do not start with + * any registered prefix. + * @param resolverClass + */ +export function registerDefaultScheme(scheme: string) { + defaultScheme = scheme; +} + +/** + * Create a name resolver for the specified target, if possible. Throws an + * error if no such name resolver can be created. + * @param target + * @param listener + */ +export function createResolver( + target: GrpcUri, + listener: ResolverListener, + options: ChannelOptions +): Resolver { + if (target.scheme !== undefined && target.scheme in registeredResolvers) { + return new registeredResolvers[target.scheme](target, listener, options); + } else { + throw new Error( + `No resolver could be created for target ${uriToString(target)}` + ); + } +} + +/** + * Get the default authority for the specified target, if possible. Throws an + * error if no registered name resolver can parse that target string. + * @param target + */ +export function getDefaultAuthority(target: GrpcUri): string { + if (target.scheme !== undefined && target.scheme in registeredResolvers) { + return registeredResolvers[target.scheme].getDefaultAuthority(target); + } else { + throw new Error(`Invalid target ${uriToString(target)}`); + } +} + +export function mapUriDefaultScheme(target: GrpcUri): GrpcUri | null { + if (target.scheme === undefined || !(target.scheme in registeredResolvers)) { + if (defaultScheme !== null) { + return { + scheme: defaultScheme, + authority: undefined, + path: uriToString(target), + }; + } else { + return null; + } + } + return target; +} diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts new file mode 100644 index 000000000..5dbe83cd9 --- /dev/null +++ b/packages/grpc-js/src/resolving-call.ts @@ -0,0 +1,279 @@ +/* + * Copyright 2022 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. + * + */ + +import { CallCredentials } from "./call-credentials"; +import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; +import { LogVerbosity, Propagate, Status } from "./constants"; +import { Deadline, deadlineToString, getRelativeTimeout, minDeadline } from "./deadline"; +import { FilterStack, FilterStackFactory } from "./filter-stack"; +import { InternalChannel } from "./internal-channel"; +import { Metadata } from "./metadata"; +import * as logging from './logging'; +import { restrictControlPlaneStatusCode } from "./control-plane-status"; + +const TRACER_NAME = 'resolving_call'; + +export class ResolvingCall implements Call { + private child: Call | null = null; + private readPending = false; + private pendingMessage: {context: MessageContext, message: Buffer} | null = null; + private pendingHalfClose = false; + private ended = false; + private readFilterPending = false; + private writeFilterPending = false; + private pendingChildStatus: StatusObject | null = null; + private metadata: Metadata | null = null; + private listener: InterceptingListener | null = null; + private deadline: Deadline; + private host: string; + private statusWatchers: ((status: StatusObject) => void)[] = []; + private deadlineTimer: NodeJS.Timeout = setTimeout(() => {}, 0); + private filterStack: FilterStack | null = null; + + constructor( + private readonly channel: InternalChannel, + private readonly method: string, + options: CallStreamOptions, + private readonly filterStackFactory: FilterStackFactory, + private credentials: CallCredentials, + private callNumber: number + ) { + this.deadline = options.deadline; + this.host = options.host; + if (options.parentCall) { + if (options.flags & Propagate.CANCELLATION) { + options.parentCall.on('cancelled', () => { + this.cancelWithStatus(Status.CANCELLED, 'Cancelled by parent call'); + }); + } + if (options.flags & Propagate.DEADLINE) { + this.trace('Propagating deadline from parent: ' + options.parentCall.getDeadline()); + this.deadline = minDeadline(this.deadline, options.parentCall.getDeadline()); + } + } + this.trace('Created'); + this.runDeadlineTimer(); + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callNumber + '] ' + text + ); + } + + private runDeadlineTimer() { + clearTimeout(this.deadlineTimer); + this.trace('Deadline: ' + deadlineToString(this.deadline)); + const timeout = getRelativeTimeout(this.deadline); + if (timeout !== Infinity) { + this.trace('Deadline will be reached in ' + timeout + 'ms'); + const handleDeadline = () => { + this.cancelWithStatus( + Status.DEADLINE_EXCEEDED, + 'Deadline exceeded' + ); + } + if (timeout <= 0) { + process.nextTick(handleDeadline); + } else { + this.deadlineTimer = setTimeout(handleDeadline, timeout); + } + } + } + + private outputStatus(status: StatusObject) { + if (!this.ended) { + this.ended = true; + if (!this.filterStack) { + this.filterStack = this.filterStackFactory.createFilter(); + } + clearTimeout(this.deadlineTimer); + const filteredStatus = this.filterStack.receiveTrailers(status); + this.trace('ended with status: code=' + filteredStatus.code + ' details="' + filteredStatus.details + '"'); + this.statusWatchers.forEach(watcher => watcher(filteredStatus)); + process.nextTick(() => { + this.listener?.onReceiveStatus(filteredStatus); + }); + } + } + + private sendMessageOnChild(context: MessageContext, message: Buffer): void { + if (!this.child) { + throw new Error('sendMessageonChild called with child not populated'); + } + const child = this.child; + this.writeFilterPending = true; + this.filterStack!.sendMessage(Promise.resolve({message: message, flags: context.flags})).then((filteredMessage) => { + this.writeFilterPending = false; + child.sendMessageWithContext(context, filteredMessage.message); + if (this.pendingHalfClose) { + child.halfClose(); + } + }, (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + }); + } + + getConfig(): void { + if (this.ended) { + return; + } + if (!this.metadata || !this.listener) { + throw new Error('getConfig called before start'); + } + const configResult = this.channel.getConfig(this.method, this.metadata); + if (configResult.type === 'NONE') { + this.channel.queueCallForConfig(this); + return; + } else if (configResult.type === 'ERROR') { + if (this.metadata.getOptions().waitForReady) { + this.channel.queueCallForConfig(this); + } else { + this.outputStatus(configResult.error); + } + return; + } + // configResult.type === 'SUCCESS' + const config = configResult.config; + if (config.status !== Status.OK) { + const {code, details} = restrictControlPlaneStatusCode(config.status, 'Failed to route call to method ' + this.method); + this.outputStatus({ + code: code, + details: details, + metadata: new Metadata() + }); + return; + } + + if (config.methodConfig.timeout) { + const configDeadline = new Date(); + configDeadline.setSeconds( + configDeadline.getSeconds() + config.methodConfig.timeout.seconds + ); + configDeadline.setMilliseconds( + configDeadline.getMilliseconds() + + config.methodConfig.timeout.nanos / 1_000_000 + ); + this.deadline = minDeadline(this.deadline, configDeadline); + this.runDeadlineTimer(); + } + + this.filterStackFactory.push(config.dynamicFilterFactories); + this.filterStack = this.filterStackFactory.createFilter(); + this.filterStack.sendMetadata(Promise.resolve(this.metadata)).then(filteredMetadata => { + this.child = this.channel.createInnerCall(config, this.method, this.host, this.credentials, this.deadline); + this.trace('Created child [' + this.child.getCallNumber() + ']') + this.child.start(filteredMetadata, { + onReceiveMetadata: metadata => { + this.trace('Received metadata') + this.listener!.onReceiveMetadata(this.filterStack!.receiveMetadata(metadata)); + }, + onReceiveMessage: message => { + this.trace('Received message'); + this.readFilterPending = true; + this.filterStack!.receiveMessage(message).then(filteredMesssage => { + this.trace('Finished filtering received message'); + this.readFilterPending = false; + this.listener!.onReceiveMessage(filteredMesssage); + if (this.pendingChildStatus) { + this.outputStatus(this.pendingChildStatus); + } + }, (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + }); + }, + onReceiveStatus: status => { + this.trace('Received status'); + if (this.readFilterPending) { + this.pendingChildStatus = status; + } else { + this.outputStatus(status); + } + } + }); + if (this.readPending) { + this.child.startRead(); + } + if (this.pendingMessage) { + this.sendMessageOnChild(this.pendingMessage.context, this.pendingMessage.message); + } else if (this.pendingHalfClose) { + this.child.halfClose(); + } + }, (status: StatusObject) => { + this.outputStatus(status); + }) + } + + reportResolverError(status: StatusObject) { + if (this.metadata?.getOptions().waitForReady) { + this.channel.queueCallForConfig(this); + } else { + this.outputStatus(status); + } + } + cancelWithStatus(status: Status, details: string): void { + this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); + this.child?.cancelWithStatus(status, details); + this.outputStatus({code: status, details: details, metadata: new Metadata()}); + } + getPeer(): string { + return this.child?.getPeer() ?? this.channel.getTarget(); + } + start(metadata: Metadata, listener: InterceptingListener): void { + this.trace('start called'); + this.metadata = metadata.clone(); + this.listener = listener; + this.getConfig(); + } + sendMessageWithContext(context: MessageContext, message: Buffer): void { + this.trace('write() called with message of length ' + message.length); + if (this.child) { + this.sendMessageOnChild(context, message); + } else { + this.pendingMessage = {context, message}; + } + } + startRead(): void { + this.trace('startRead called'); + if (this.child) { + this.child.startRead(); + } else { + this.readPending = true; + } + } + halfClose(): void { + this.trace('halfClose called'); + if (this.child && !this.writeFilterPending) { + this.child.halfClose(); + } else { + this.pendingHalfClose = true; + } + } + setCredentials(credentials: CallCredentials): void { + this.credentials = this.credentials.compose(credentials); + } + + addStatusWatcher(watcher: (status: StatusObject) => void) { + this.statusWatchers.push(watcher); + } + + getCallNumber(): number { + return this.callNumber; + } +} diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts new file mode 100644 index 000000000..5194bef46 --- /dev/null +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -0,0 +1,330 @@ +/* + * Copyright 2019 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. + * + */ + +import { + ChannelControlHelper, + LoadBalancer, + LoadBalancingConfig, + getFirstUsableConfig, +} from './load-balancer'; +import { ServiceConfig, validateServiceConfig } from './service-config'; +import { ConnectivityState } from './connectivity-state'; +import { ConfigSelector, createResolver, Resolver } from './resolver'; +import { ServiceError } from './call'; +import { Picker, UnavailablePicker, QueuePicker } from './picker'; +import { BackoffOptions, BackoffTimeout } from './backoff-timeout'; +import { Status } from './constants'; +import { StatusObject } from './call-interface'; +import { Metadata } from './metadata'; +import * as logging from './logging'; +import { LogVerbosity } from './constants'; +import { SubchannelAddress } from './subchannel-address'; +import { GrpcUri, uriToString } from './uri-parser'; +import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; +import { ChannelOptions } from './channel-options'; + +const TRACER_NAME = 'resolving_load_balancer'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +function getDefaultConfigSelector( + serviceConfig: ServiceConfig | null +): ConfigSelector { + return function defaultConfigSelector( + methodName: string, + metadata: Metadata + ) { + const splitName = methodName.split('/').filter((x) => x.length > 0); + const service = splitName[0] ?? ''; + const method = splitName[1] ?? ''; + if (serviceConfig && serviceConfig.methodConfig) { + for (const methodConfig of serviceConfig.methodConfig) { + for (const name of methodConfig.name) { + if ( + name.service === service && + (name.method === undefined || name.method === method) + ) { + return { + methodConfig: methodConfig, + pickInformation: {}, + status: Status.OK, + dynamicFilterFactories: [] + }; + } + } + } + } + return { + methodConfig: { name: [] }, + pickInformation: {}, + status: Status.OK, + dynamicFilterFactories: [] + }; + }; +} + +export interface ResolutionCallback { + (serviceConfig: ServiceConfig, configSelector: ConfigSelector): void; +} + +export interface ResolutionFailureCallback { + (status: StatusObject): void; +} + +export class ResolvingLoadBalancer implements LoadBalancer { + /** + * The resolver class constructed for the target address. + */ + private innerResolver: Resolver; + + private childLoadBalancer: ChildLoadBalancerHandler; + private latestChildState: ConnectivityState = ConnectivityState.IDLE; + private latestChildPicker: Picker = new QueuePicker(this); + /** + * This resolving load balancer's current connectivity state. + */ + private currentState: ConnectivityState = ConnectivityState.IDLE; + private readonly defaultServiceConfig: ServiceConfig; + /** + * The service config object from the last successful resolution, if + * available. A value of null indicates that we have not yet received a valid + * service config from the resolver. + */ + private previousServiceConfig: ServiceConfig | null = null; + + /** + * The backoff timer for handling name resolution failures. + */ + private readonly backoffTimeout: BackoffTimeout; + + /** + * Indicates whether we should attempt to resolve again after the backoff + * timer runs out. + */ + private continueResolving = false; + + /** + * Wrapper class that behaves like a `LoadBalancer` and also handles name + * resolution internally. + * @param target The address of the backend to connect to. + * @param channelControlHelper `ChannelControlHelper` instance provided by + * this load balancer's owner. + * @param defaultServiceConfig The default service configuration to be used + * if none is provided by the name resolver. A `null` value indicates + * that the default behavior should be the default unconfigured behavior. + * In practice, that means using the "pick first" load balancer + * implmentation + */ + constructor( + private readonly target: GrpcUri, + private readonly channelControlHelper: ChannelControlHelper, + channelOptions: ChannelOptions, + private readonly onSuccessfulResolution: ResolutionCallback, + private readonly onFailedResolution: ResolutionFailureCallback + ) { + if (channelOptions['grpc.service_config']) { + this.defaultServiceConfig = validateServiceConfig( + JSON.parse(channelOptions['grpc.service_config']!) + ); + } else { + this.defaultServiceConfig = { + loadBalancingConfig: [], + methodConfig: [], + }; + } + this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); + this.childLoadBalancer = new ChildLoadBalancerHandler({ + createSubchannel: channelControlHelper.createSubchannel.bind( + channelControlHelper + ), + requestReresolution: () => { + /* If the backoffTimeout is running, we're still backing off from + * making resolve requests, so we shouldn't make another one here. + * In that case, the backoff timer callback will call + * updateResolution */ + if (this.backoffTimeout.isRunning()) { + this.continueResolving = true; + } else { + this.updateResolution(); + } + }, + updateState: (newState: ConnectivityState, picker: Picker) => { + this.latestChildState = newState; + this.latestChildPicker = picker; + this.updateState(newState, picker); + }, + addChannelzChild: channelControlHelper.addChannelzChild.bind( + channelControlHelper + ), + removeChannelzChild: channelControlHelper.removeChannelzChild.bind( + channelControlHelper + ) + }); + this.innerResolver = createResolver( + target, + { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: ServiceError | null, + configSelector: ConfigSelector | null, + attributes: { [key: string]: unknown } + ) => { + let workingServiceConfig: ServiceConfig | null = null; + /* This first group of conditionals implements the algorithm described + * in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md + * in the section called "Behavior on receiving a new gRPC Config". + */ + if (serviceConfig === null) { + // Step 4 and 5 + if (serviceConfigError === null) { + // Step 5 + this.previousServiceConfig = null; + workingServiceConfig = this.defaultServiceConfig; + } else { + // Step 4 + if (this.previousServiceConfig === null) { + // Step 4.ii + this.handleResolutionFailure(serviceConfigError); + } else { + // Step 4.i + workingServiceConfig = this.previousServiceConfig; + } + } + } else { + // Step 3 + workingServiceConfig = serviceConfig; + this.previousServiceConfig = serviceConfig; + } + const workingConfigList = + workingServiceConfig?.loadBalancingConfig ?? []; + const loadBalancingConfig = getFirstUsableConfig( + workingConfigList, + true + ); + if (loadBalancingConfig === null) { + // There were load balancing configs but none are supported. This counts as a resolution failure + this.handleResolutionFailure({ + code: Status.UNAVAILABLE, + details: + 'All load balancer options in service config are not compatible', + metadata: new Metadata(), + }); + return; + } + this.childLoadBalancer.updateAddressList( + addressList, + loadBalancingConfig, + attributes + ); + const finalServiceConfig = + workingServiceConfig ?? this.defaultServiceConfig; + this.onSuccessfulResolution( + finalServiceConfig, + configSelector ?? getDefaultConfigSelector(finalServiceConfig) + ); + }, + onError: (error: StatusObject) => { + this.handleResolutionFailure(error); + }, + }, + channelOptions + ); + const backoffOptions: BackoffOptions = { + initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'], + maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'], + }; + this.backoffTimeout = new BackoffTimeout(() => { + if (this.continueResolving) { + this.updateResolution(); + this.continueResolving = false; + } else { + this.updateState(this.latestChildState, this.latestChildPicker); + } + }, backoffOptions); + this.backoffTimeout.unref(); + } + + private updateResolution() { + this.innerResolver.updateResolution(); + if (this.currentState === ConnectivityState.IDLE) { + this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); + } + this.backoffTimeout.runOnce(); + } + + private updateState(connectivityState: ConnectivityState, picker: Picker) { + trace( + uriToString(this.target) + + ' ' + + ConnectivityState[this.currentState] + + ' -> ' + + ConnectivityState[connectivityState] + ); + // Ensure that this.exitIdle() is called by the picker + if (connectivityState === ConnectivityState.IDLE) { + picker = new QueuePicker(this); + } + this.currentState = connectivityState; + this.channelControlHelper.updateState(connectivityState, picker); + } + + private handleResolutionFailure(error: StatusObject) { + if (this.latestChildState === ConnectivityState.IDLE) { + this.updateState( + ConnectivityState.TRANSIENT_FAILURE, + new UnavailablePicker(error) + ); + this.onFailedResolution(error); + } + } + + exitIdle() { + if (this.currentState === ConnectivityState.IDLE || this.currentState === ConnectivityState.TRANSIENT_FAILURE) { + if (this.backoffTimeout.isRunning()) { + this.continueResolving = true; + } else { + this.updateResolution(); + } + } + this.childLoadBalancer.exitIdle(); + } + + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig | null + ): never { + throw new Error('updateAddressList not supported on ResolvingLoadBalancer'); + } + + resetBackoff() { + this.backoffTimeout.reset(); + this.childLoadBalancer.resetBackoff(); + } + + destroy() { + this.childLoadBalancer.destroy(); + this.innerResolver.destroy(); + this.updateState(ConnectivityState.SHUTDOWN, new UnavailablePicker()); + } + + getTypeName() { + return 'resolving_load_balancer'; + } +} diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts new file mode 100644 index 000000000..bf15c9143 --- /dev/null +++ b/packages/grpc-js/src/retrying-call.ts @@ -0,0 +1,671 @@ +/* + * Copyright 2022 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. + * + */ + +import { CallCredentials } from "./call-credentials"; +import { LogVerbosity, Status } from "./constants"; +import { Deadline } from "./deadline"; +import { Metadata } from "./metadata"; +import { CallConfig } from "./resolver"; +import * as logging from './logging'; +import { Call, InterceptingListener, MessageContext, StatusObject, WriteCallback, WriteObject } from "./call-interface"; +import { LoadBalancingCall, StatusObjectWithProgress } from "./load-balancing-call"; +import { InternalChannel } from "./internal-channel"; + +const TRACER_NAME = 'retrying_call'; + +export class RetryThrottler { + private tokens: number; + constructor(private readonly maxTokens: number, private readonly tokenRatio: number, previousRetryThrottler?: RetryThrottler) { + if (previousRetryThrottler) { + /* When carrying over tokens from a previous config, rescale them to the + * new max value */ + this.tokens = previousRetryThrottler.tokens * (maxTokens / previousRetryThrottler.maxTokens); + } else { + this.tokens = maxTokens; + } + } + + addCallSucceeded() { + this.tokens = Math.max(this.tokens + this.tokenRatio, this.maxTokens); + } + + addCallFailed() { + this.tokens = Math.min(this.tokens - 1, 0); + } + + canRetryCall() { + return this.tokens > this.maxTokens / 2; + } +} + +export class MessageBufferTracker { + private totalAllocated: number = 0; + private allocatedPerCall: Map = new Map(); + + constructor(private totalLimit: number, private limitPerCall: number) {} + + allocate(size: number, callId: number): boolean { + const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; + if (this.limitPerCall - currentPerCall < size || this.totalLimit - this.totalAllocated < size) { + return false; + } + this.allocatedPerCall.set(callId, currentPerCall + size); + this.totalAllocated += size; + return true; + } + + free(size: number, callId: number) { + if (this.totalAllocated < size) { + throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > total allocated ${this.totalAllocated}`); + } + this.totalAllocated -= size; + const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; + if (currentPerCall < size) { + throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > allocated for call ${currentPerCall}`); + } + this.allocatedPerCall.set(callId, currentPerCall - size); + } + + freeAll(callId: number) { + const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; + if (this.totalAllocated < currentPerCall) { + throw new Error(`Invalid buffer allocation state: call ${callId} allocated ${currentPerCall} > total allocated ${this.totalAllocated}`); + } + this.totalAllocated -= currentPerCall; + this.allocatedPerCall.delete(callId); + } +} + +type UnderlyingCallState = 'ACTIVE' | 'COMPLETED'; + +interface UnderlyingCall { + state: UnderlyingCallState; + call: LoadBalancingCall; + nextMessageToSend: number; +} + +/** + * A retrying call can be in one of these states: + * RETRY: Retries are configured and new attempts may be sent + * HEDGING: Hedging is configured and new attempts may be sent + * TRANSPARENT_ONLY: Neither retries nor hedging are configured, and + * transparent retry attempts may still be sent + * COMMITTED: One attempt is committed, and no new attempts will be + * sent + */ +type RetryingCallState = 'RETRY' | 'HEDGING' | 'TRANSPARENT_ONLY' | 'COMMITTED'; + +/** + * The different types of objects that can be stored in the write buffer, with + * the following meanings: + * MESSAGE: This is a message to be sent. + * HALF_CLOSE: When this entry is reached, the calls should send a half-close. + * FREED: This slot previously contained a message that has been sent on all + * child calls and is no longer needed. + */ +type WriteBufferEntryType = 'MESSAGE' | 'HALF_CLOSE' | 'FREED'; + +/** + * Entry in the buffer of messages to send to the remote end. + */ +interface WriteBufferEntry { + entryType: WriteBufferEntryType; + /** + * Message to send. + * Only populated if entryType is MESSAGE. + */ + message?: WriteObject; + /** + * Callback to call after sending the message. + * Only populated if entryType is MESSAGE and the call is in the COMMITTED + * state. + */ + callback?: WriteCallback; + /** + * Indicates whether the message is allocated in the buffer tracker. Ignored + * if entryType is not MESSAGE. Should be the return value of + * bufferTracker.allocate. + */ + allocated: boolean; +} + +const PREVIONS_RPC_ATTEMPTS_METADATA_KEY = 'grpc-previous-rpc-attempts'; + +export class RetryingCall implements Call { + private state: RetryingCallState; + private listener: InterceptingListener | null = null; + private initialMetadata: Metadata | null = null; + private underlyingCalls: UnderlyingCall[] = []; + private writeBuffer: WriteBufferEntry[] = []; + /** + * The offset of message indices in the writeBuffer. For example, if + * writeBufferOffset is 10, message 10 is in writeBuffer[0] and message 15 + * is in writeBuffer[5]. + */ + private writeBufferOffset = 0; + /** + * Tracks whether a read has been started, so that we know whether to start + * reads on new child calls. This only matters for the first read, because + * once a message comes in the child call becomes committed and there will + * be no new child calls. + */ + private readStarted = false; + private transparentRetryUsed: boolean = false; + /** + * Number of attempts so far + */ + private attempts: number = 0; + private hedgingTimer: NodeJS.Timeout | null = null; + private committedCallIndex: number | null = null; + private initialRetryBackoffSec = 0; + private nextRetryBackoffSec = 0; + constructor( + private readonly channel: InternalChannel, + private readonly callConfig: CallConfig, + private readonly methodName: string, + private readonly host: string, + private readonly credentials: CallCredentials, + private readonly deadline: Deadline, + private readonly callNumber: number, + private readonly bufferTracker: MessageBufferTracker, + private readonly retryThrottler?: RetryThrottler + ) { + if (callConfig.methodConfig.retryPolicy) { + this.state = 'RETRY'; + const retryPolicy = callConfig.methodConfig.retryPolicy; + this.nextRetryBackoffSec = this.initialRetryBackoffSec = Number(retryPolicy.initialBackoff.substring(0, retryPolicy.initialBackoff.length - 1)); + } else if (callConfig.methodConfig.hedgingPolicy) { + this.state = 'HEDGING'; + } else { + this.state = 'TRANSPARENT_ONLY'; + } + } + getCallNumber(): number { + return this.callNumber; + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callNumber + '] ' + text + ); + } + + private reportStatus(statusObject: StatusObject) { + this.trace('ended with status: code=' + statusObject.code + ' details="' + statusObject.details + '"'); + this.bufferTracker.freeAll(this.callNumber); + this.writeBufferOffset = this.writeBufferOffset + this.writeBuffer.length; + this.writeBuffer = []; + process.nextTick(() => { + // Explicitly construct status object to remove progress field + this.listener?.onReceiveStatus({ + code: statusObject.code, + details: statusObject.details, + metadata: statusObject.metadata + }); + }); + } + + cancelWithStatus(status: Status, details: string): void { + this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); + this.reportStatus({code: status, details, metadata: new Metadata()}); + for (const {call} of this.underlyingCalls) { + call.cancelWithStatus(status, details); + } + } + getPeer(): string { + if (this.committedCallIndex !== null) { + return this.underlyingCalls[this.committedCallIndex].call.getPeer(); + } else { + return 'unknown'; + } + } + + private getBufferEntry(messageIndex: number): WriteBufferEntry { + return this.writeBuffer[messageIndex - this.writeBufferOffset] ?? {entryType: 'FREED', allocated: false}; + } + + private getNextBufferIndex() { + return this.writeBufferOffset + this.writeBuffer.length; + } + + private clearSentMessages() { + if (this.state !== 'COMMITTED') { + return; + } + const earliestNeededMessageIndex = this.underlyingCalls[this.committedCallIndex!].nextMessageToSend; + for (let messageIndex = this.writeBufferOffset; messageIndex < earliestNeededMessageIndex; messageIndex++) { + const bufferEntry = this.getBufferEntry(messageIndex); + if (bufferEntry.allocated) { + this.bufferTracker.free(bufferEntry.message!.message.length, this.callNumber); + } + } + this.writeBuffer = this.writeBuffer.slice(earliestNeededMessageIndex - this.writeBufferOffset); + this.writeBufferOffset = earliestNeededMessageIndex; + } + + private commitCall(index: number) { + if (this.state === 'COMMITTED') { + return; + } + if (this.underlyingCalls[index].state === 'COMPLETED') { + return; + } + this.trace('Committing call [' + this.underlyingCalls[index].call.getCallNumber() + '] at index ' + index); + this.state = 'COMMITTED'; + this.committedCallIndex = index; + for (let i = 0; i < this.underlyingCalls.length; i++) { + if (i === index) { + continue; + } + if (this.underlyingCalls[i].state === 'COMPLETED') { + continue; + } + this.underlyingCalls[i].state = 'COMPLETED'; + this.underlyingCalls[i].call.cancelWithStatus(Status.CANCELLED, 'Discarded in favor of other hedged attempt'); + } + this.clearSentMessages(); + } + + private commitCallWithMostMessages() { + if (this.state === 'COMMITTED') { + return; + } + let mostMessages = -1; + let callWithMostMessages = -1; + for (const [index, childCall] of this.underlyingCalls.entries()) { + if (childCall.state === 'ACTIVE' && childCall.nextMessageToSend > mostMessages) { + mostMessages = childCall.nextMessageToSend; + callWithMostMessages = index; + } + } + if (callWithMostMessages === -1) { + /* There are no active calls, disable retries to force the next call that + * is started to be committed. */ + this.state = 'TRANSPARENT_ONLY'; + } else { + this.commitCall(callWithMostMessages); + } + } + + private isStatusCodeInList(list: (Status | string)[], code: Status) { + return list.some((value => value === code || value.toString().toLowerCase() === Status[code].toLowerCase())); + } + + private getNextRetryBackoffMs() { + const retryPolicy = this.callConfig?.methodConfig.retryPolicy; + if (!retryPolicy) { + return 0; + } + const nextBackoffMs = Math.random() * this.nextRetryBackoffSec * 1000; + const maxBackoffSec = Number(retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1)); + this.nextRetryBackoffSec = Math.min(this.nextRetryBackoffSec * retryPolicy.backoffMultiplier, maxBackoffSec); + return nextBackoffMs + } + + private maybeRetryCall(pushback: number | null, callback: (retried: boolean) => void) { + if (this.state !== 'RETRY') { + callback(false); + return; + } + const retryPolicy = this.callConfig!.methodConfig.retryPolicy!; + if (this.attempts >= Math.min(retryPolicy.maxAttempts, 5)) { + callback(false); + return; + } + let retryDelayMs: number; + if (pushback === null) { + retryDelayMs = this.getNextRetryBackoffMs(); + } else if (pushback < 0) { + this.state = 'TRANSPARENT_ONLY'; + callback(false); + return; + } else { + retryDelayMs = pushback; + this.nextRetryBackoffSec = this.initialRetryBackoffSec; + } + setTimeout(() => { + if (this.state !== 'RETRY') { + callback(false); + return; + } + if (this.retryThrottler?.canRetryCall() ?? true) { + callback(true); + this.attempts += 1; + this.startNewAttempt(); + } + }, retryDelayMs); + } + + private countActiveCalls(): number { + let count = 0; + for (const call of this.underlyingCalls) { + if (call?.state === 'ACTIVE') { + count += 1; + } + } + return count; + } + + private handleProcessedStatus(status: StatusObject, callIndex: number, pushback: number | null) { + switch (this.state) { + case 'COMMITTED': + case 'TRANSPARENT_ONLY': + this.commitCall(callIndex); + this.reportStatus(status); + break; + case 'HEDGING': + if (this.isStatusCodeInList(this.callConfig!.methodConfig.hedgingPolicy!.nonFatalStatusCodes ?? [], status.code)) { + this.retryThrottler?.addCallFailed(); + let delayMs: number; + if (pushback === null) { + delayMs = 0; + } else if (pushback < 0) { + this.state = 'TRANSPARENT_ONLY'; + this.commitCall(callIndex); + this.reportStatus(status); + return; + } else { + delayMs = pushback; + } + setTimeout(() => { + this.maybeStartHedgingAttempt(); + // If after trying to start a call there are no active calls, this was the last one + if (this.countActiveCalls() === 0) { + this.commitCall(callIndex); + this.reportStatus(status); + } + }, delayMs); + } else { + this.commitCall(callIndex); + this.reportStatus(status); + } + break; + case 'RETRY': + if (this.isStatusCodeInList(this.callConfig!.methodConfig.retryPolicy!.retryableStatusCodes, status.code)) { + this.retryThrottler?.addCallFailed(); + this.maybeRetryCall(pushback, (retried) => { + if (!retried) { + this.commitCall(callIndex); + this.reportStatus(status); + } + }); + } else { + this.commitCall(callIndex); + this.reportStatus(status); + } + break; + } + } + + private getPushback(metadata: Metadata): number | null { + const mdValue = metadata.get('grpc-retry-pushback-ms'); + if (mdValue.length === 0) { + return null; + } + try { + return parseInt(mdValue[0] as string); + } catch (e) { + return -1; + } + } + + private handleChildStatus(status: StatusObjectWithProgress, callIndex: number) { + if (this.underlyingCalls[callIndex].state === 'COMPLETED') { + return; + } + this.trace('state=' + this.state + ' handling status with progress ' + status.progress + ' from child [' + this.underlyingCalls[callIndex].call.getCallNumber() + '] in state ' + this.underlyingCalls[callIndex].state); + this.underlyingCalls[callIndex].state = 'COMPLETED'; + if (status.code === Status.OK) { + this.retryThrottler?.addCallSucceeded(); + this.commitCall(callIndex); + this.reportStatus(status); + return; + } + if (this.state === 'COMMITTED') { + this.reportStatus(status); + return; + } + const pushback = this.getPushback(status.metadata); + switch (status.progress) { + case 'NOT_STARTED': + // RPC never leaves the client, always safe to retry + this.startNewAttempt(); + break; + case 'REFUSED': + // RPC reaches the server library, but not the server application logic + if (this.transparentRetryUsed) { + this.handleProcessedStatus(status, callIndex, pushback); + } else { + this.transparentRetryUsed = true; + this.startNewAttempt(); + }; + break; + case 'DROP': + this.commitCall(callIndex); + this.reportStatus(status); + break; + case 'PROCESSED': + this.handleProcessedStatus(status, callIndex, pushback); + break; + } + } + + private maybeStartHedgingAttempt() { + if (this.state !== 'HEDGING') { + return; + } + if (!this.callConfig.methodConfig.hedgingPolicy) { + return; + } + const hedgingPolicy = this.callConfig.methodConfig.hedgingPolicy; + if (this.attempts >= Math.min(hedgingPolicy.maxAttempts, 5)) { + return; + } + this.attempts += 1; + this.startNewAttempt(); + this.maybeStartHedgingTimer(); + } + + private maybeStartHedgingTimer() { + if (this.hedgingTimer) { + clearTimeout(this.hedgingTimer); + } + if (this.state !== 'HEDGING') { + return; + } + if (!this.callConfig.methodConfig.hedgingPolicy) { + return; + } + const hedgingPolicy = this.callConfig.methodConfig.hedgingPolicy; + if (this.attempts >= Math.min(hedgingPolicy.maxAttempts, 5)) { + return; + } + const hedgingDelayString = hedgingPolicy.hedgingDelay ?? '0s'; + const hedgingDelaySec = Number(hedgingDelayString.substring(0, hedgingDelayString.length - 1)); + this.hedgingTimer = setTimeout(() => { + this.maybeStartHedgingAttempt(); + }, hedgingDelaySec * 1000); + this.hedgingTimer.unref?.(); + } + + private startNewAttempt() { + const child = this.channel.createLoadBalancingCall(this.callConfig, this.methodName, this.host, this.credentials, this.deadline); + this.trace('Created child call [' + child.getCallNumber() + '] for attempt ' + this.attempts); + const index = this.underlyingCalls.length; + this.underlyingCalls.push({state: 'ACTIVE', call: child, nextMessageToSend: 0}); + const previousAttempts = this.attempts - 1; + const initialMetadata = this.initialMetadata!.clone(); + if (previousAttempts > 0) { + initialMetadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + } + let receivedMetadata = false; + child.start(initialMetadata, { + onReceiveMetadata: metadata => { + this.trace('Received metadata from child [' + child.getCallNumber() + ']'); + this.commitCall(index); + receivedMetadata = true; + if (previousAttempts > 0) { + metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + } + if (this.underlyingCalls[index].state === 'ACTIVE') { + this.listener!.onReceiveMetadata(metadata); + } + }, + onReceiveMessage: message => { + this.trace('Received message from child [' + child.getCallNumber() + ']'); + this.commitCall(index); + if (this.underlyingCalls[index].state === 'ACTIVE') { + this.listener!.onReceiveMessage(message); + } + }, + onReceiveStatus: status => { + this.trace('Received status from child [' + child.getCallNumber() + ']'); + if (!receivedMetadata && previousAttempts > 0) { + status.metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + } + this.handleChildStatus(status, index); + } + }); + this.sendNextChildMessage(index); + if (this.readStarted) { + child.startRead(); + } + } + + start(metadata: Metadata, listener: InterceptingListener): void { + this.trace('start called'); + this.listener = listener; + this.initialMetadata = metadata; + this.attempts += 1; + this.startNewAttempt(); + this.maybeStartHedgingTimer(); + } + + private handleChildWriteCompleted(childIndex: number) { + const childCall = this.underlyingCalls[childIndex]; + const messageIndex = childCall.nextMessageToSend; + this.getBufferEntry(messageIndex).callback?.(); + this.clearSentMessages(); + childCall.nextMessageToSend += 1; + this.sendNextChildMessage(childIndex); + } + + private sendNextChildMessage(childIndex: number) { + const childCall = this.underlyingCalls[childIndex]; + if (childCall.state === 'COMPLETED') { + return; + } + if (this.getBufferEntry(childCall.nextMessageToSend)) { + const bufferEntry = this.getBufferEntry(childCall.nextMessageToSend); + switch (bufferEntry.entryType) { + case 'MESSAGE': + childCall.call.sendMessageWithContext({ + callback: (error) => { + // Ignore error + this.handleChildWriteCompleted(childIndex); + } + }, bufferEntry.message!.message); + break; + case 'HALF_CLOSE': + childCall.nextMessageToSend += 1; + childCall.call.halfClose(); + break; + case 'FREED': + // Should not be possible + break; + } + } + } + + sendMessageWithContext(context: MessageContext, message: Buffer): void { + this.trace('write() called with message of length ' + message.length); + const writeObj: WriteObject = { + message, + flags: context.flags, + }; + const messageIndex = this.getNextBufferIndex(); + const bufferEntry: WriteBufferEntry = { + entryType: 'MESSAGE', + message: writeObj, + allocated: this.bufferTracker.allocate(message.length, this.callNumber) + }; + this.writeBuffer.push(bufferEntry); + if (bufferEntry.allocated) { + context.callback?.(); + for (const [callIndex, call] of this.underlyingCalls.entries()) { + if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { + call.call.sendMessageWithContext({ + callback: (error) => { + // Ignore error + this.handleChildWriteCompleted(callIndex); + } + }, message); + } + } + } else { + this.commitCallWithMostMessages(); + // commitCallWithMostMessages can fail if we are between ping attempts + if (this.committedCallIndex === null) { + return; + } + const call = this.underlyingCalls[this.committedCallIndex]; + bufferEntry.callback = context.callback; + if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { + call.call.sendMessageWithContext({ + callback: (error) => { + // Ignore error + this.handleChildWriteCompleted(this.committedCallIndex!); + } + }, message); + } + } + } + startRead(): void { + this.trace('startRead called'); + this.readStarted = true; + for (const underlyingCall of this.underlyingCalls) { + if (underlyingCall?.state === 'ACTIVE') { + underlyingCall.call.startRead(); + } + } + } + halfClose(): void { + this.trace('halfClose called'); + const halfCloseIndex = this.getNextBufferIndex(); + this.writeBuffer.push({ + entryType: 'HALF_CLOSE', + allocated: false + }); + for (const call of this.underlyingCalls) { + if (call?.state === 'ACTIVE' && call.nextMessageToSend === halfCloseIndex) { + call.nextMessageToSend += 1; + call.call.halfClose(); + } + } + } + setCredentials(newCredentials: CallCredentials): void { + throw new Error("Method not implemented."); + } + getMethod(): string { + return this.methodName; + } + getHost(): string { + return this.host; + } +} diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts new file mode 100644 index 000000000..b06feda14 --- /dev/null +++ b/packages/grpc-js/src/server-call.ts @@ -0,0 +1,1013 @@ +/* + * Copyright 2019 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. + * + */ + +import { EventEmitter } from 'events'; +import * as http2 from 'http2'; +import { Duplex, Readable, Writable } from 'stream'; +import * as zlib from 'zlib'; + +import { + Status, + DEFAULT_MAX_SEND_MESSAGE_LENGTH, + DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, + LogVerbosity, +} from './constants'; +import { Deserialize, Serialize } from './make-client'; +import { Metadata } from './metadata'; +import { StreamDecoder } from './stream-decoder'; +import { ObjectReadable, ObjectWritable } from './object-stream'; +import { ChannelOptions } from './channel-options'; +import * as logging from './logging'; +import { StatusObject, PartialStatusObject } from './call-interface'; +import { Deadline } from './deadline'; +import { getErrorCode, getErrorMessage } from './error'; + +const TRACER_NAME = 'server_call'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +interface DeadlineUnitIndexSignature { + [name: string]: number; +} + +const GRPC_ACCEPT_ENCODING_HEADER = 'grpc-accept-encoding'; +const GRPC_ENCODING_HEADER = 'grpc-encoding'; +const GRPC_MESSAGE_HEADER = 'grpc-message'; +const GRPC_STATUS_HEADER = 'grpc-status'; +const GRPC_TIMEOUT_HEADER = 'grpc-timeout'; +const DEADLINE_REGEX = /(\d{1,8})\s*([HMSmun])/; +const deadlineUnitsToMs: DeadlineUnitIndexSignature = { + H: 3600000, + M: 60000, + S: 1000, + m: 1, + u: 0.001, + n: 0.000001, +}; +const defaultCompressionHeaders = { + // TODO(cjihrig): Remove these encoding headers from the default response + // once compression is integrated. + [GRPC_ACCEPT_ENCODING_HEADER]: 'identity,deflate,gzip', + [GRPC_ENCODING_HEADER]: 'identity', +} +const defaultResponseHeaders = { + [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, + [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto', +}; +const defaultResponseOptions = { + waitForTrailers: true, +} as http2.ServerStreamResponseOptions; + +export type ServerStatusResponse = Partial; + +export type ServerErrorResponse = ServerStatusResponse & Error; + +export type ServerSurfaceCall = { + cancelled: boolean; + readonly metadata: Metadata; + getPeer(): string; + sendMetadata(responseMetadata: Metadata): void; + getDeadline(): Deadline; + getPath(): string; +} & EventEmitter; + +export type ServerUnaryCall = ServerSurfaceCall & { + request: RequestType; +}; +export type ServerReadableStream = + ServerSurfaceCall & ObjectReadable; +export type ServerWritableStream = + ServerSurfaceCall & + ObjectWritable & { + request: RequestType; + end: (metadata?: Metadata) => void; + }; +export type ServerDuplexStream = ServerSurfaceCall & + ObjectReadable & + ObjectWritable & { end: (metadata?: Metadata) => void }; + +export class ServerUnaryCallImpl + extends EventEmitter + implements ServerUnaryCall +{ + cancelled: boolean; + + constructor( + private call: Http2ServerCallStream, + public metadata: Metadata, + public request: RequestType + ) { + super(); + this.cancelled = false; + this.call.setupSurfaceCall(this); + } + + getPeer(): string { + return this.call.getPeer(); + } + + sendMetadata(responseMetadata: Metadata): void { + this.call.sendMetadata(responseMetadata); + } + + getDeadline(): Deadline { + return this.call.getDeadline(); + } + + getPath(): string { + return this.call.getPath(); + } +} + +export class ServerReadableStreamImpl + extends Readable + implements ServerReadableStream +{ + cancelled: boolean; + + constructor( + private call: Http2ServerCallStream, + public metadata: Metadata, + public deserialize: Deserialize, + encoding: string + ) { + super({ objectMode: true }); + this.cancelled = false; + this.call.setupSurfaceCall(this); + this.call.setupReadable(this, encoding); + } + + _read(size: number) { + if (!this.call.consumeUnpushedMessages(this)) { + return; + } + + this.call.resume(); + } + + getPeer(): string { + return this.call.getPeer(); + } + + sendMetadata(responseMetadata: Metadata): void { + this.call.sendMetadata(responseMetadata); + } + + getDeadline(): Deadline { + return this.call.getDeadline(); + } + + getPath(): string { + return this.call.getPath(); + } +} + +export class ServerWritableStreamImpl + extends Writable + implements ServerWritableStream +{ + cancelled: boolean; + private trailingMetadata: Metadata; + + constructor( + private call: Http2ServerCallStream, + public metadata: Metadata, + public serialize: Serialize, + public request: RequestType + ) { + super({ objectMode: true }); + this.cancelled = false; + this.trailingMetadata = new Metadata(); + this.call.setupSurfaceCall(this); + + this.on('error', (err) => { + this.call.sendError(err); + this.end(); + }); + } + + getPeer(): string { + return this.call.getPeer(); + } + + sendMetadata(responseMetadata: Metadata): void { + this.call.sendMetadata(responseMetadata); + } + + getDeadline(): Deadline { + return this.call.getDeadline(); + } + + getPath(): string { + return this.call.getPath(); + } + + _write( + chunk: ResponseType, + encoding: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback: (...args: any[]) => void + ) { + try { + const response = this.call.serializeMessage(chunk); + + if (!this.call.write(response)) { + this.call.once('drain', callback); + return; + } + } catch (err) { + this.emit('error', { + details: getErrorMessage(err), + code: Status.INTERNAL + }); + } + + callback(); + } + + _final(callback: Function): void { + this.call.sendStatus({ + code: Status.OK, + details: 'OK', + metadata: this.trailingMetadata, + }); + callback(null); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + end(metadata?: any) { + if (metadata) { + this.trailingMetadata = metadata; + } + + return super.end(); + } +} + +export class ServerDuplexStreamImpl + extends Duplex + implements ServerDuplexStream +{ + cancelled: boolean; + /* This field appears to be unsued, but it is actually used in _final, which is assiged from + * ServerWritableStreamImpl.prototype._final below. */ + // @ts-ignore noUnusedLocals + private trailingMetadata: Metadata; + + constructor( + private call: Http2ServerCallStream, + public metadata: Metadata, + public serialize: Serialize, + public deserialize: Deserialize, + encoding: string + ) { + super({ objectMode: true }); + this.cancelled = false; + this.trailingMetadata = new Metadata(); + this.call.setupSurfaceCall(this); + this.call.setupReadable(this, encoding); + + this.on('error', (err) => { + this.call.sendError(err); + this.end(); + }); + } + + getPeer(): string { + return this.call.getPeer(); + } + + sendMetadata(responseMetadata: Metadata): void { + this.call.sendMetadata(responseMetadata); + } + + getDeadline(): Deadline { + return this.call.getDeadline(); + } + + getPath(): string { + return this.call.getPath(); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + end(metadata?: any) { + if (metadata) { + this.trailingMetadata = metadata; + } + + return super.end(); + } +} + +ServerDuplexStreamImpl.prototype._read = + ServerReadableStreamImpl.prototype._read; +ServerDuplexStreamImpl.prototype._write = + ServerWritableStreamImpl.prototype._write; +ServerDuplexStreamImpl.prototype._final = + ServerWritableStreamImpl.prototype._final; + +// Unary response callback signature. +export type sendUnaryData = ( + error: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + trailer?: Metadata, + flags?: number +) => void; + +// User provided handler for unary calls. +export type handleUnaryCall = ( + call: ServerUnaryCall, + callback: sendUnaryData +) => void; + +// User provided handler for client streaming calls. +export type handleClientStreamingCall = ( + call: ServerReadableStream, + callback: sendUnaryData +) => void; + +// User provided handler for server streaming calls. +export type handleServerStreamingCall = ( + call: ServerWritableStream +) => void; + +// User provided handler for bidirectional streaming calls. +export type handleBidiStreamingCall = ( + call: ServerDuplexStream +) => void; + +export type HandleCall = + | handleUnaryCall + | handleClientStreamingCall + | handleServerStreamingCall + | handleBidiStreamingCall; + +export interface UnaryHandler { + func: handleUnaryCall; + serialize: Serialize; + deserialize: Deserialize; + type: HandlerType; + path: string; +} + +export interface ClientStreamingHandler { + func: handleClientStreamingCall; + serialize: Serialize; + deserialize: Deserialize; + type: HandlerType; + path: string; +} + +export interface ServerStreamingHandler { + func: handleServerStreamingCall; + serialize: Serialize; + deserialize: Deserialize; + type: HandlerType; + path: string; +} + +export interface BidiStreamingHandler { + func: handleBidiStreamingCall; + serialize: Serialize; + deserialize: Deserialize; + type: HandlerType; + path: string; +} + +export type Handler = + | UnaryHandler + | ClientStreamingHandler + | ServerStreamingHandler + | BidiStreamingHandler; + +export type HandlerType = 'bidi' | 'clientStream' | 'serverStream' | 'unary'; + +// Internal class that wraps the HTTP2 request. +export class Http2ServerCallStream< + RequestType, + ResponseType +> extends EventEmitter { + cancelled = false; + deadlineTimer: NodeJS.Timeout | null = null; + private statusSent = false; + private deadline: Deadline = Infinity; + private wantTrailers = false; + private metadataSent = false; + private canPush = false; + private isPushPending = false; + private bufferedMessages: Array = []; + private messagesToPush: Array = []; + private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; + private maxReceiveMessageSize: number = DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; + + constructor( + private stream: http2.ServerHttp2Stream, + private handler: Handler, + options: ChannelOptions + ) { + super(); + + this.stream.once('error', (err: ServerErrorResponse) => { + /* We need an error handler to avoid uncaught error event exceptions, but + * there is nothing we can reasonably do here. Any error event should + * have a corresponding close event, which handles emitting the cancelled + * event. And the stream is now in a bad state, so we can't reasonably + * expect to be able to send an error over it. */ + }); + + this.stream.once('close', () => { + trace( + 'Request to method ' + + this.handler?.path + + ' stream closed with rstCode ' + + this.stream.rstCode + ); + + if (!this.statusSent) { + this.cancelled = true; + this.emit('cancelled', 'cancelled'); + this.emit('streamEnd', false); + this.sendStatus({ + code: Status.CANCELLED, + details: 'Cancelled by client', + metadata: null, + }); + } + }); + + this.stream.on('drain', () => { + this.emit('drain'); + }); + + if ('grpc.max_send_message_length' in options) { + this.maxSendMessageSize = options['grpc.max_send_message_length']!; + } + if ('grpc.max_receive_message_length' in options) { + this.maxReceiveMessageSize = options['grpc.max_receive_message_length']!; + } + } + + private checkCancelled(): boolean { + /* In some cases the stream can become destroyed before the close event + * fires. That creates a race condition that this check works around */ + if (this.stream.destroyed || this.stream.closed) { + this.cancelled = true; + } + return this.cancelled; + } + + private getDecompressedMessage( + message: Buffer, + encoding: string + ): Buffer | Promise { const messageContents = message.subarray(5); + if (encoding === 'identity') { + return messageContents; + } else if (encoding === 'deflate' || encoding === 'gzip') { + let decompresser: zlib.Gunzip | zlib.Deflate; + if (encoding === 'deflate') { + decompresser = zlib.createInflate(); + } else { + decompresser = zlib.createGunzip(); + } + return new Promise((resolve, reject) => { + let totalLength = 0 + const messageParts: Buffer[] = []; + decompresser.on('data', (chunk: Buffer) => { + messageParts.push(chunk); + totalLength += chunk.byteLength; + if (this.maxReceiveMessageSize !== -1 && totalLength > this.maxReceiveMessageSize) { + decompresser.destroy(); + reject({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message that decompresses to a size larger than ${this.maxReceiveMessageSize}` + }); + } + }); + decompresser.on('end', () => { + resolve(Buffer.concat(messageParts)); + }); + decompresser.write(messageContents); + decompresser.end(); + }); + } else { + return Promise.reject({ + code: Status.UNIMPLEMENTED, + details: `Received message compressed with unsupported encoding "${encoding}"`, + }); + } + } + + sendMetadata(customMetadata?: Metadata) { + if (this.checkCancelled()) { + return; + } + + if (this.metadataSent) { + return; + } + + this.metadataSent = true; + const custom = customMetadata ? customMetadata.toHttp2Headers() : null; + // TODO(cjihrig): Include compression headers. + const headers = { ...defaultResponseHeaders, ...defaultCompressionHeaders, ...custom }; + this.stream.respond(headers, defaultResponseOptions); + } + + receiveMetadata(headers: http2.IncomingHttpHeaders) { + const metadata = Metadata.fromHttp2Headers(headers); + + if (logging.isTracerEnabled(TRACER_NAME)) { + trace( + 'Request to ' + + this.handler.path + + ' received headers ' + + JSON.stringify(metadata.toJSON()) + ); + } + + // TODO(cjihrig): Receive compression metadata. + + const timeoutHeader = metadata.get(GRPC_TIMEOUT_HEADER); + + if (timeoutHeader.length > 0) { + const match = timeoutHeader[0].toString().match(DEADLINE_REGEX); + + if (match === null) { + const err = new Error('Invalid deadline') as ServerErrorResponse; + err.code = Status.OUT_OF_RANGE; + this.sendError(err); + return metadata; + } + + const timeout = (+match[1] * deadlineUnitsToMs[match[2]]) | 0; + + const now = new Date(); + this.deadline = now.setMilliseconds(now.getMilliseconds() + timeout); + this.deadlineTimer = setTimeout(handleExpiredDeadline, timeout, this); + metadata.remove(GRPC_TIMEOUT_HEADER); + } + + // Remove several headers that should not be propagated to the application + metadata.remove(http2.constants.HTTP2_HEADER_ACCEPT_ENCODING); + metadata.remove(http2.constants.HTTP2_HEADER_TE); + metadata.remove(http2.constants.HTTP2_HEADER_CONTENT_TYPE); + metadata.remove('grpc-accept-encoding'); + + return metadata; + } + + receiveUnaryMessage( + encoding: string, + next: ( + err: Partial | null, + request?: RequestType + ) => void + ): void { + const { stream } = this; + + let receivedLength = 0; + const call = this; + const body: Buffer[] = []; + const limit = this.maxReceiveMessageSize; + + stream.on('data', onData); + stream.on('end', onEnd); + stream.on('error', onEnd); + + function onData(chunk: Buffer) { + receivedLength += chunk.byteLength; + + if (limit !== -1 && receivedLength > limit) { + stream.removeListener('data', onData); + stream.removeListener('end', onEnd); + stream.removeListener('error', onEnd); + next({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message larger than max (${receivedLength} vs. ${limit})`, + }); + return; + } + + body.push(chunk); + } + + function onEnd(err?: Error) { + stream.removeListener('data', onData); + stream.removeListener('end', onEnd); + stream.removeListener('error', onEnd); + + if (err !== undefined) { + next({ code: Status.INTERNAL, details: err.message }); + return; + } + + if (receivedLength === 0) { + next({ code: Status.INTERNAL, details: 'received empty unary message' }) + return; + } + + call.emit('receiveMessage'); + + const requestBytes = Buffer.concat(body, receivedLength); + const compressed = requestBytes.readUInt8(0) === 1; + const compressedMessageEncoding = compressed ? encoding : 'identity'; + const decompressedMessage = call.getDecompressedMessage( + requestBytes, + compressedMessageEncoding + ); + + if (Buffer.isBuffer(decompressedMessage)) { + call.safeDeserializeMessage(decompressedMessage, next); + return; + } + + decompressedMessage.then( + (decompressed) => call.safeDeserializeMessage(decompressed, next), + (err: any) => next( + err.code + ? err + : { + code: Status.INTERNAL, + details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, + } + ) + ) + } + } + + private safeDeserializeMessage( + buffer: Buffer, + next: (err: Partial | null, request?: RequestType) => void + ) { + try { + next(null, this.deserializeMessage(buffer)); + } catch (err) { + next({ + details: getErrorMessage(err), + code: Status.INTERNAL + }); + } + } + + serializeMessage(value: ResponseType) { + const messageBuffer = this.handler.serialize(value); + + // TODO(cjihrig): Call compression aware serializeMessage(). + const byteLength = messageBuffer.byteLength; + const output = Buffer.allocUnsafe(byteLength + 5); + output.writeUInt8(0, 0); + output.writeUInt32BE(byteLength, 1); + messageBuffer.copy(output, 5); + return output; + } + + deserializeMessage(bytes: Buffer) { + return this.handler.deserialize(bytes); + } + + async sendUnaryMessage( + err: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + metadata?: Metadata | null, + flags?: number + ) { + if (this.checkCancelled()) { + return; + } + + if (metadata === undefined) { + metadata = null; + } + + if (err) { + if (!Object.prototype.hasOwnProperty.call(err, 'metadata') && metadata) { + err.metadata = metadata; + } + this.sendError(err); + return; + } + + try { + const response = this.serializeMessage(value!); + + this.write(response); + this.sendStatus({ code: Status.OK, details: 'OK', metadata }); + } catch (err) { + this.sendError({ + details: getErrorMessage(err), + code: Status.INTERNAL + }); + } + } + + sendStatus(statusObj: PartialStatusObject) { + this.emit('callEnd', statusObj.code); + this.emit('streamEnd', statusObj.code === Status.OK); + if (this.checkCancelled()) { + return; + } + + trace( + 'Request to method ' + + this.handler?.path + + ' ended with status code: ' + + Status[statusObj.code] + + ' details: ' + + statusObj.details + ); + + if (this.deadlineTimer) clearTimeout(this.deadlineTimer); + + if (this.stream.headersSent) { + if (!this.wantTrailers) { + this.wantTrailers = true; + this.stream.once('wantTrailers', () => { + const trailersToSend = { + [GRPC_STATUS_HEADER]: statusObj.code, + [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details), + ...statusObj.metadata?.toHttp2Headers(), + }; + + this.stream.sendTrailers(trailersToSend); + this.statusSent = true; + }); + this.stream.end(); + } + } else { + // Trailers-only response + const trailersToSend = { + [GRPC_STATUS_HEADER]: statusObj.code, + [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details), + ...defaultResponseHeaders, + ...statusObj.metadata?.toHttp2Headers(), + }; + this.stream.respond(trailersToSend, {endStream: true}); + this.statusSent = true; + } + } + + sendError(error: ServerErrorResponse | ServerStatusResponse) { + const status: PartialStatusObject = { + code: Status.UNKNOWN, + details: 'message' in error ? error.message : 'Unknown Error', + metadata: + 'metadata' in error && error.metadata !== undefined + ? error.metadata + : null, + }; + + if ( + 'code' in error && + typeof error.code === 'number' && + Number.isInteger(error.code) + ) { + status.code = error.code; + + if ('details' in error && typeof error.details === 'string') { + status.details = error.details!; + } + } + + this.sendStatus(status); + } + + write(chunk: Buffer) { + if (this.checkCancelled()) { + return; + } + + if ( + this.maxSendMessageSize !== -1 && + chunk.length > this.maxSendMessageSize + ) { + this.sendError({ + code: Status.RESOURCE_EXHAUSTED, + details: `Sent message larger than max (${chunk.length} vs. ${this.maxSendMessageSize})`, + }); + return; + } + + this.sendMetadata(); + this.emit('sendMessage'); + return this.stream.write(chunk); + } + + resume() { + this.stream.resume(); + } + + setupSurfaceCall(call: ServerSurfaceCall) { + this.once('cancelled', (reason) => { + call.cancelled = true; + call.emit('cancelled', reason); + }); + + this.once('callEnd', (status) => call.emit('callEnd', status)); + } + + setupReadable( + readable: + | ServerReadableStream + | ServerDuplexStream, + encoding: string + ) { + const decoder = new StreamDecoder(this.maxReceiveMessageSize); + + let readsDone = false; + + let pendingMessageProcessing = false; + + let pushedEnd = false; + + const maybePushEnd = async () => { + if (!pushedEnd && readsDone && !pendingMessageProcessing) { + pushedEnd = true; + await this.pushOrBufferMessage(readable, null); + } + }; + + this.stream.on('data', async (data: Buffer) => { + let messages: Buffer[]; + try { + messages = decoder.write(data); + } catch (e) { + this.sendError({ + code: Status.RESOURCE_EXHAUSTED, + details: (e as Error).message + }); + return; + } + + pendingMessageProcessing = true; + this.stream.pause(); + for (const message of messages) { + this.emit('receiveMessage'); + + const compressed = message.readUInt8(0) === 1; + const compressedMessageEncoding = compressed ? encoding : 'identity'; + let decompressedMessage: Buffer; + try { + decompressedMessage = await this.getDecompressedMessage( + message, + compressedMessageEncoding + ); + } catch (e) { + this.sendError(e as Partial); + return; + } + + // Encountered an error with decompression; it'll already have been propogated back + // Just return early + if (!decompressedMessage) return; + + await this.pushOrBufferMessage(readable, decompressedMessage); + } + pendingMessageProcessing = false; + this.stream.resume(); + await maybePushEnd(); + }); + + this.stream.once('end', async () => { + readsDone = true; + await maybePushEnd(); + }); + } + + consumeUnpushedMessages( + readable: + | ServerReadableStream + | ServerDuplexStream + ): boolean { + this.canPush = true; + + while (this.messagesToPush.length > 0) { + const nextMessage = this.messagesToPush.shift(); + const canPush = readable.push(nextMessage); + + if (nextMessage === null || canPush === false) { + this.canPush = false; + break; + } + } + + return this.canPush; + } + + private async pushOrBufferMessage( + readable: + | ServerReadableStream + | ServerDuplexStream, + messageBytes: Buffer | null + ): Promise { + if (this.isPushPending) { + this.bufferedMessages.push(messageBytes); + } else { + await this.pushMessage(readable, messageBytes); + } + } + + private async pushMessage( + readable: + | ServerReadableStream + | ServerDuplexStream, + messageBytes: Buffer | null + ) { + if (messageBytes === null) { + trace('Received end of stream'); + if (this.canPush) { + readable.push(null); + } else { + this.messagesToPush.push(null); + } + + return; + } + + trace('Received message of length ' + messageBytes.length); + + this.isPushPending = true; + + try { + const deserialized = await this.deserializeMessage(messageBytes); + + if (this.canPush) { + if (!readable.push(deserialized)) { + this.canPush = false; + this.stream.pause(); + } + } else { + this.messagesToPush.push(deserialized); + } + } catch (error) { + // Ignore any remaining messages when errors occur. + this.bufferedMessages.length = 0; + let code = getErrorCode(error); + if (code === null || code < Status.OK || code > Status.UNAUTHENTICATED) { + code = Status.INTERNAL + } + + readable.emit('error', { + details: getErrorMessage(error), + code: code + }); + } + + this.isPushPending = false; + + if (this.bufferedMessages.length > 0) { + await this.pushMessage( + readable, + this.bufferedMessages.shift() as Buffer | null + ); + } + } + + getPeer(): string { + const socket = this.stream.session?.socket; + if (socket?.remoteAddress) { + if (socket.remotePort) { + return `${socket.remoteAddress}:${socket.remotePort}`; + } else { + return socket.remoteAddress; + } + } else { + return 'unknown'; + } + } + + getDeadline(): Deadline { + return this.deadline; + } + + getPath(): string { + return this.handler.path; + } +} + +/* eslint-disable @typescript-eslint/no-explicit-any */ +type UntypedServerCall = Http2ServerCallStream; + +function handleExpiredDeadline(call: UntypedServerCall) { + const err = new Error('Deadline exceeded') as ServerErrorResponse; + err.code = Status.DEADLINE_EXCEEDED; + + call.sendError(err); + call.cancelled = true; + call.emit('cancelled', 'deadline'); +} diff --git a/packages/grpc-js/src/server-credentials.ts b/packages/grpc-js/src/server-credentials.ts new file mode 100644 index 000000000..17ab29805 --- /dev/null +++ b/packages/grpc-js/src/server-credentials.ts @@ -0,0 +1,108 @@ +/* + * Copyright 2019 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. + * + */ + +import { SecureServerOptions } from 'http2'; +import { CIPHER_SUITES, getDefaultRootsData } from './tls-helpers'; + +export interface KeyCertPair { + private_key: Buffer; + cert_chain: Buffer; +} + +export abstract class ServerCredentials { + abstract _isSecure(): boolean; + abstract _getSettings(): SecureServerOptions | null; + + static createInsecure(): ServerCredentials { + return new InsecureServerCredentials(); + } + + static createSsl( + rootCerts: Buffer | null, + keyCertPairs: KeyCertPair[], + checkClientCertificate = false + ): ServerCredentials { + if (rootCerts !== null && !Buffer.isBuffer(rootCerts)) { + throw new TypeError('rootCerts must be null or a Buffer'); + } + + if (!Array.isArray(keyCertPairs)) { + throw new TypeError('keyCertPairs must be an array'); + } + + if (typeof checkClientCertificate !== 'boolean') { + throw new TypeError('checkClientCertificate must be a boolean'); + } + + const cert = []; + const key = []; + + for (let i = 0; i < keyCertPairs.length; i++) { + const pair = keyCertPairs[i]; + + if (pair === null || typeof pair !== 'object') { + throw new TypeError(`keyCertPair[${i}] must be an object`); + } + + if (!Buffer.isBuffer(pair.private_key)) { + throw new TypeError(`keyCertPair[${i}].private_key must be a Buffer`); + } + + if (!Buffer.isBuffer(pair.cert_chain)) { + throw new TypeError(`keyCertPair[${i}].cert_chain must be a Buffer`); + } + + cert.push(pair.cert_chain); + key.push(pair.private_key); + } + + return new SecureServerCredentials({ + ca: rootCerts || getDefaultRootsData() || undefined, + cert, + key, + requestCert: checkClientCertificate, + ciphers: CIPHER_SUITES, + }); + } +} + +class InsecureServerCredentials extends ServerCredentials { + _isSecure(): boolean { + return false; + } + + _getSettings(): null { + return null; + } +} + +class SecureServerCredentials extends ServerCredentials { + private options: SecureServerOptions; + + constructor(options: SecureServerOptions) { + super(); + this.options = options; + } + + _isSecure(): boolean { + return true; + } + + _getSettings(): SecureServerOptions { + return this.options; + } +} diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts new file mode 100644 index 000000000..1b481bbf9 --- /dev/null +++ b/packages/grpc-js/src/server.ts @@ -0,0 +1,1156 @@ +/* + * Copyright 2019 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. + * + */ + +import * as http2 from 'http2'; +import { AddressInfo } from 'net'; + +import { ServiceError } from './call'; +import { Status, LogVerbosity } from './constants'; +import { Deserialize, Serialize, ServiceDefinition } from './make-client'; +import { Metadata } from './metadata'; +import { + BidiStreamingHandler, + ClientStreamingHandler, + HandleCall, + Handler, + HandlerType, + Http2ServerCallStream, + sendUnaryData, + ServerDuplexStream, + ServerDuplexStreamImpl, + ServerReadableStream, + ServerReadableStreamImpl, + ServerStreamingHandler, + ServerUnaryCall, + ServerUnaryCallImpl, + ServerWritableStream, + ServerWritableStreamImpl, + UnaryHandler, + ServerErrorResponse, + ServerStatusResponse, +} from './server-call'; +import { ServerCredentials } from './server-credentials'; +import { ChannelOptions } from './channel-options'; +import { + createResolver, + ResolverListener, + mapUriDefaultScheme, +} from './resolver'; +import * as logging from './logging'; +import { + SubchannelAddress, + TcpSubchannelAddress, + isTcpSubchannelAddress, + subchannelAddressToString, + stringToSubchannelAddress, +} from './subchannel-address'; +import { parseUri } from './uri-parser'; +import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; +import { CipherNameAndProtocol, TLSSocket } from 'tls'; + +const UNLIMITED_CONNECTION_AGE_MS = ~(1<<31); +const KEEPALIVE_MAX_TIME_MS = ~(1<<31); +const KEEPALIVE_TIMEOUT_MS = 20000; + +const { + HTTP2_HEADER_PATH +} = http2.constants + +const TRACER_NAME = 'server'; + +interface BindResult { + port: number; + count: number; +} + +function noop(): void {} + +function getUnimplementedStatusResponse( + methodName: string +): Partial { + return { + code: Status.UNIMPLEMENTED, + details: `The server does not implement the method ${methodName}`, + }; +} + +/* eslint-disable @typescript-eslint/no-explicit-any */ +type UntypedUnaryHandler = UnaryHandler; +type UntypedClientStreamingHandler = ClientStreamingHandler; +type UntypedServerStreamingHandler = ServerStreamingHandler; +type UntypedBidiStreamingHandler = BidiStreamingHandler; +export type UntypedHandleCall = HandleCall; +type UntypedHandler = Handler; +export interface UntypedServiceImplementation { + [name: string]: UntypedHandleCall; +} + +function getDefaultHandler(handlerType: HandlerType, methodName: string) { + const unimplementedStatusResponse = getUnimplementedStatusResponse( + methodName + ); + switch (handlerType) { + case 'unary': + return ( + call: ServerUnaryCall, + callback: sendUnaryData + ) => { + callback(unimplementedStatusResponse as ServiceError, null); + }; + case 'clientStream': + return ( + call: ServerReadableStream, + callback: sendUnaryData + ) => { + callback(unimplementedStatusResponse as ServiceError, null); + }; + case 'serverStream': + return (call: ServerWritableStream) => { + call.emit('error', unimplementedStatusResponse); + }; + case 'bidi': + return (call: ServerDuplexStream) => { + call.emit('error', unimplementedStatusResponse); + }; + default: + throw new Error(`Invalid handlerType ${handlerType}`); + } +} + +interface ChannelzSessionInfo { + ref: SocketRef; + streamTracker: ChannelzCallTracker; + messagesSent: number; + messagesReceived: number; + lastMessageSentTimestamp: Date | null; + lastMessageReceivedTimestamp: Date | null; +} + +export class Server { + private http2ServerList: { server: (http2.Http2Server | http2.Http2SecureServer), channelzRef: SocketRef }[] = []; + + private handlers: Map = new Map< + string, + UntypedHandler + >(); + private sessions = new Map(); + private started = false; + private options: ChannelOptions; + private serverAddressString: string = 'null' + + // Channelz Info + private readonly channelzEnabled: boolean = true; + private channelzRef: ServerRef; + private channelzTrace = new ChannelzTrace(); + private callTracker = new ChannelzCallTracker(); + private listenerChildrenTracker = new ChannelzChildrenTracker(); + private sessionChildrenTracker = new ChannelzChildrenTracker(); + + private readonly maxConnectionAgeMs: number; + private readonly maxConnectionAgeGraceMs: number; + + private readonly keepaliveTimeMs: number; + private readonly keepaliveTimeoutMs: number; + + constructor(options?: ChannelOptions) { + this.options = options ?? {}; + if (this.options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + this.channelzRef = registerChannelzServer(() => this.getChannelzInfo(), this.channelzEnabled); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Server created'); + } + this.maxConnectionAgeMs = this.options['grpc.max_connection_age_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; + this.maxConnectionAgeGraceMs = this.options['grpc.max_connection_age_grace_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; + this.keepaliveTimeMs = this.options['grpc.keepalive_time_ms'] ?? KEEPALIVE_MAX_TIME_MS; + this.keepaliveTimeoutMs = this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; + this.trace('Server constructed'); + } + + private getChannelzInfo(): ServerInfo { + return { + trace: this.channelzTrace, + callTracker: this.callTracker, + listenerChildren: this.listenerChildrenTracker.getChildLists(), + sessionChildren: this.sessionChildrenTracker.getChildLists() + }; + } + + private getChannelzSessionInfoGetter(session: http2.ServerHttp2Session): () => SocketInfo { + return () => { + const sessionInfo = this.sessions.get(session)!; + const sessionSocket = session.socket; + const remoteAddress = sessionSocket.remoteAddress ? stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; + const localAddress = sessionSocket.localAddress ? stringToSubchannelAddress(sessionSocket.localAddress!, sessionSocket.localPort) : null; + let tlsInfo: TlsInfo | null; + if (session.encrypted) { + const tlsSocket: TLSSocket = sessionSocket as TLSSocket; + const cipherInfo: CipherNameAndProtocol & {standardName?: string} = tlsSocket.getCipher(); + const certificate = tlsSocket.getCertificate(); + const peerCertificate = tlsSocket.getPeerCertificate(); + tlsInfo = { + cipherSuiteStandardName: cipherInfo.standardName ?? null, + cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, + localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, + remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null + }; + } else { + tlsInfo = null; + } + const socketInfo: SocketInfo = { + remoteAddress: remoteAddress, + localAddress: localAddress, + security: tlsInfo, + remoteName: null, + streamsStarted: sessionInfo.streamTracker.callsStarted, + streamsSucceeded: sessionInfo.streamTracker.callsSucceeded, + streamsFailed: sessionInfo.streamTracker.callsFailed, + messagesSent: sessionInfo.messagesSent, + messagesReceived: sessionInfo.messagesReceived, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: sessionInfo.streamTracker.lastCallStartedTimestamp, + lastMessageSentTimestamp: sessionInfo.lastMessageSentTimestamp, + lastMessageReceivedTimestamp: sessionInfo.lastMessageReceivedTimestamp, + localFlowControlWindow: session.state.localWindowSize ?? null, + remoteFlowControlWindow: session.state.remoteWindowSize ?? null + }; + return socketInfo; + }; + } + + private trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + text); + } + + + addProtoService(): never { + throw new Error('Not implemented. Use addService() instead'); + } + + addService( + service: ServiceDefinition, + implementation: UntypedServiceImplementation + ): void { + if ( + service === null || + typeof service !== 'object' || + implementation === null || + typeof implementation !== 'object' + ) { + throw new Error('addService() requires two objects as arguments'); + } + + const serviceKeys = Object.keys(service); + + if (serviceKeys.length === 0) { + throw new Error('Cannot add an empty service to a server'); + } + + serviceKeys.forEach((name) => { + const attrs = service[name]; + let methodType: HandlerType; + + if (attrs.requestStream) { + if (attrs.responseStream) { + methodType = 'bidi'; + } else { + methodType = 'clientStream'; + } + } else { + if (attrs.responseStream) { + methodType = 'serverStream'; + } else { + methodType = 'unary'; + } + } + + let implFn = implementation[name]; + let impl; + + if (implFn === undefined && typeof attrs.originalName === 'string') { + implFn = implementation[attrs.originalName]; + } + + if (implFn !== undefined) { + impl = implFn.bind(implementation); + } else { + impl = getDefaultHandler(methodType, name); + } + + const success = this.register( + attrs.path, + impl as UntypedHandleCall, + attrs.responseSerialize, + attrs.requestDeserialize, + methodType + ); + + if (success === false) { + throw new Error(`Method handler for ${attrs.path} already provided.`); + } + }); + } + + removeService(service: ServiceDefinition): void { + if (service === null || typeof service !== 'object') { + throw new Error('removeService() requires object as argument'); + } + + const serviceKeys = Object.keys(service); + serviceKeys.forEach((name) => { + const attrs = service[name]; + this.unregister(attrs.path); + }); + } + + bind(port: string, creds: ServerCredentials): never { + throw new Error('Not implemented. Use bindAsync() instead'); + } + + bindAsync( + port: string, + creds: ServerCredentials, + callback: (error: Error | null, port: number) => void + ): void { + if (this.started === true) { + throw new Error('server is already started'); + } + + if (typeof port !== 'string') { + throw new TypeError('port must be a string'); + } + + if (creds === null || !(creds instanceof ServerCredentials)) { + throw new TypeError('creds must be a ServerCredentials object'); + } + + if (typeof callback !== 'function') { + throw new TypeError('callback must be a function'); + } + + const initialPortUri = parseUri(port); + if (initialPortUri === null) { + throw new Error(`Could not parse port "${port}"`); + } + const portUri = mapUriDefaultScheme(initialPortUri); + if (portUri === null) { + throw new Error(`Could not get a default scheme for port "${port}"`); + } + + const serverOptions: http2.ServerOptions = { + maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, + }; + if ('grpc-node.max_session_memory' in this.options) { + serverOptions.maxSessionMemory = this.options[ + 'grpc-node.max_session_memory' + ]; + } else { + /* By default, set a very large max session memory limit, to effectively + * disable enforcement of the limit. Some testing indicates that Node's + * behavior degrades badly when this limit is reached, so we solve that + * by disabling the check entirely. */ + serverOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; + } + if ('grpc.max_concurrent_streams' in this.options) { + serverOptions.settings = { + maxConcurrentStreams: this.options['grpc.max_concurrent_streams'], + }; + } + + const deferredCallback = (error: Error | null, port: number) => { + process.nextTick(() => callback(error, port)); + } + + const setupServer = (): http2.Http2Server | http2.Http2SecureServer => { + let http2Server: http2.Http2Server | http2.Http2SecureServer; + if (creds._isSecure()) { + const secureServerOptions = Object.assign( + serverOptions, + creds._getSettings()! + ); + http2Server = http2.createSecureServer(secureServerOptions); + http2Server.on('secureConnection', (socket: TLSSocket) => { + /* These errors need to be handled by the user of Http2SecureServer, + * according to https://github.com/nodejs/node/issues/35824 */ + socket.on('error', (e: Error) => { + this.trace('An incoming TLS connection closed with error: ' + e.message); + }); + }); + } else { + http2Server = http2.createServer(serverOptions); + } + + http2Server.setTimeout(0, noop); + this._setupHandlers(http2Server); + return http2Server; + }; + + const bindSpecificPort = ( + addressList: SubchannelAddress[], + portNum: number, + previousCount: number + ): Promise => { + if (addressList.length === 0) { + return Promise.resolve({ port: portNum, count: previousCount }); + } + return Promise.all( + addressList.map((address) => { + this.trace('Attempting to bind ' + subchannelAddressToString(address)); + let addr: SubchannelAddress; + if (isTcpSubchannelAddress(address)) { + addr = { + host: (address as TcpSubchannelAddress).host, + port: portNum, + }; + } else { + addr = address; + } + + const http2Server = setupServer(); + return new Promise((resolve, reject) => { + const onError = (err: Error) => { + this.trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); + resolve(err); + } + + http2Server.once('error', onError); + + http2Server.listen(addr, () => { + const boundAddress = http2Server.address()!; + let boundSubchannelAddress: SubchannelAddress; + if (typeof boundAddress === 'string') { + boundSubchannelAddress = { + path: boundAddress + }; + } else { + boundSubchannelAddress = { + host: boundAddress.address, + port: boundAddress.port + } + } + let channelzRef: SocketRef; + channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null + }; + }, this.channelzEnabled); + if (this.channelzEnabled) { + this.listenerChildrenTracker.refChild(channelzRef); + } + this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); + this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); + resolve('port' in boundSubchannelAddress ? boundSubchannelAddress.port : portNum); + http2Server.removeListener('error', onError); + }); + }); + }) + ).then((results) => { + let count = 0; + for (const result of results) { + if (typeof result === 'number') { + count += 1; + if (result !== portNum) { + throw new Error( + 'Invalid state: multiple port numbers added from single address' + ); + } + } + } + return { + port: portNum, + count: count + previousCount, + }; + }); + }; + + const bindWildcardPort = ( + addressList: SubchannelAddress[] + ): Promise => { + if (addressList.length === 0) { + return Promise.resolve({ port: 0, count: 0 }); + } + const address = addressList[0]; + const http2Server = setupServer(); + return new Promise((resolve, reject) => { + const onError = (err: Error) => { + this.trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); + resolve(bindWildcardPort(addressList.slice(1))); + } + + http2Server.once('error', onError); + + http2Server.listen(address, () => { + const boundAddress = http2Server.address() as AddressInfo; + const boundSubchannelAddress: SubchannelAddress = { + host: boundAddress.address, + port: boundAddress.port + }; + let channelzRef: SocketRef; + channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null + }; + }, this.channelzEnabled); + if (this.channelzEnabled) { + this.listenerChildrenTracker.refChild(channelzRef); + } + this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); + this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); + resolve( + bindSpecificPort( + addressList.slice(1), + boundAddress.port, + 1 + ) + ); + http2Server.removeListener('error', onError); + }); + }); + }; + + const resolverListener: ResolverListener = { + onSuccessfulResolution: ( + addressList, + serviceConfig, + serviceConfigError + ) => { + // We only want one resolution result. Discard all future results + resolverListener.onSuccessfulResolution = () => {}; + if (addressList.length === 0) { + deferredCallback(new Error(`No addresses resolved for port ${port}`), 0); + return; + } + let bindResultPromise: Promise; + if (isTcpSubchannelAddress(addressList[0])) { + if (addressList[0].port === 0) { + bindResultPromise = bindWildcardPort(addressList); + } else { + bindResultPromise = bindSpecificPort( + addressList, + addressList[0].port, + 0 + ); + } + } else { + // Use an arbitrary non-zero port for non-TCP addresses + bindResultPromise = bindSpecificPort(addressList, 1, 0); + } + bindResultPromise.then( + (bindResult) => { + if (bindResult.count === 0) { + const errorString = `No address added out of total ${addressList.length} resolved`; + logging.log(LogVerbosity.ERROR, errorString); + deferredCallback(new Error(errorString), 0); + } else { + if (bindResult.count < addressList.length) { + logging.log( + LogVerbosity.INFO, + `WARNING Only ${bindResult.count} addresses added out of total ${addressList.length} resolved` + ); + } + deferredCallback(null, bindResult.port); + } + }, + (error) => { + const errorString = `No address added out of total ${addressList.length} resolved`; + logging.log(LogVerbosity.ERROR, errorString); + deferredCallback(new Error(errorString), 0); + } + ); + }, + onError: (error) => { + deferredCallback(new Error(error.details), 0); + }, + }; + + const resolver = createResolver(portUri, resolverListener, this.options); + resolver.updateResolution(); + } + + forceShutdown(): void { + // Close the server if it is still running. + + for (const {server: http2Server, channelzRef: ref} of this.http2ServerList) { + if (http2Server.listening) { + http2Server.close(() => { + if (this.channelzEnabled) { + this.listenerChildrenTracker.unrefChild(ref); + unregisterChannelzRef(ref); + } + }); + } + } + + this.started = false; + + // Always destroy any available sessions. It's possible that one or more + // tryShutdown() calls are in progress. Don't wait on them to finish. + this.sessions.forEach((channelzInfo, session) => { + // Cast NGHTTP2_CANCEL to any because TypeScript doesn't seem to + // recognize destroy(code) as a valid signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + session.destroy(http2.constants.NGHTTP2_CANCEL as any); + }); + this.sessions.clear(); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } + } + + register( + name: string, + handler: HandleCall, + serialize: Serialize, + deserialize: Deserialize, + type: string + ): boolean { + if (this.handlers.has(name)) { + return false; + } + + this.handlers.set(name, { + func: handler, + serialize, + deserialize, + type, + path: name, + } as UntypedHandler); + return true; + } + + unregister(name: string): boolean { + return this.handlers.delete(name); + } + + start(): void { + if ( + this.http2ServerList.length === 0 || + this.http2ServerList.every( + ({server: http2Server}) => http2Server.listening !== true + ) + ) { + throw new Error('server must be bound in order to start'); + } + + if (this.started === true) { + throw new Error('server is already started'); + } + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Starting'); + } + this.started = true; + } + + tryShutdown(callback: (error?: Error) => void): void { + const wrappedCallback = (error?: Error) => { + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } + callback(error); + }; + let pendingChecks = 0; + + function maybeCallback(): void { + pendingChecks--; + + if (pendingChecks === 0) { + wrappedCallback(); + } + } + + // Close the server if necessary. + this.started = false; + + for (const {server: http2Server, channelzRef: ref} of this.http2ServerList) { + if (http2Server.listening) { + pendingChecks++; + http2Server.close(() => { + if (this.channelzEnabled) { + this.listenerChildrenTracker.unrefChild(ref); + unregisterChannelzRef(ref); + } + maybeCallback(); + }); + } + } + + this.sessions.forEach((channelzInfo, session) => { + if (!session.closed) { + pendingChecks += 1; + session.close(maybeCallback); + } + }); + if (pendingChecks === 0) { + wrappedCallback(); + } + } + + addHttp2Port(): never { + throw new Error('Not yet implemented'); + } + + /** + * Get the channelz reference object for this server. The returned value is + * garbage if channelz is disabled for this server. + * @returns + */ + getChannelzRef() { + return this.channelzRef; + } + + private _verifyContentType(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders): boolean { + const contentType = headers[http2.constants.HTTP2_HEADER_CONTENT_TYPE]; + + if ( + typeof contentType !== 'string' || + !contentType.startsWith('application/grpc') + ) { + stream.respond( + { + [http2.constants.HTTP2_HEADER_STATUS]: + http2.constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, + }, + { endStream: true } + ); + return false + } + + return true + } + + private _retrieveHandler(path: string): Handler | null { + this.trace( + 'Received call to method ' + + path + + ' at address ' + + this.serverAddressString + ); + + const handler = this.handlers.get(path); + + if (handler === undefined) { + this.trace( + 'No handler registered for method ' + + path + + '. Sending UNIMPLEMENTED status.' + ); + return null; + } + + return handler + } + + private _respondWithError>( + err: T, + stream: http2.ServerHttp2Stream, + channelzSessionInfo: ChannelzSessionInfo | null = null + ) { + const call = new Http2ServerCallStream(stream, null!, this.options); + + if (err.code === undefined) { + err.code = Status.INTERNAL; + } + + if (this.channelzEnabled) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed() + } + + call.sendError(err); + } + + private _channelzHandler(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) { + const channelzSessionInfo = this.sessions.get(stream.session as http2.ServerHttp2Session); + + this.callTracker.addCallStarted(); + channelzSessionInfo?.streamTracker.addCallStarted(); + + if (!this._verifyContentType(stream, headers)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); + return + } + + const path = headers[HTTP2_HEADER_PATH] as string; + + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError(getUnimplementedStatusResponse(path), stream, channelzSessionInfo); + return; + } + + const call = new Http2ServerCallStream(stream, handler, this.options); + + call.once('callEnd', (code: Status) => { + if (code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); + + if (channelzSessionInfo) { + call.once('streamEnd', (success: boolean) => { + if (success) { + channelzSessionInfo.streamTracker.addCallSucceeded(); + } else { + channelzSessionInfo.streamTracker.addCallFailed(); + } + }); + call.on('sendMessage', () => { + channelzSessionInfo.messagesSent += 1; + channelzSessionInfo.lastMessageSentTimestamp = new Date(); + }); + call.on('receiveMessage', () => { + channelzSessionInfo.messagesReceived += 1; + channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); + }); + } + + if (!this._runHandlerForCall(call, handler, headers)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed() + + call.sendError({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}` + }); + } + } + + private _streamHandler(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) { + if (this._verifyContentType(stream, headers) !== true) { + return + } + + + const path = headers[HTTP2_HEADER_PATH] as string; + + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError(getUnimplementedStatusResponse(path), stream, null); + return; + } + + const call = new Http2ServerCallStream(stream, handler, this.options) + if (!this._runHandlerForCall(call, handler, headers)) { + call.sendError({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}` + }); + } + } + + private _runHandlerForCall(call: Http2ServerCallStream, handler: Handler, headers: http2.IncomingHttpHeaders): boolean { + const metadata = call.receiveMetadata(headers); + const encoding = (metadata.get('grpc-encoding')[0] as string | undefined) ?? 'identity'; + metadata.remove('grpc-encoding'); + + const { type } = handler + if (type === 'unary') { + handleUnary(call, handler as UntypedUnaryHandler, metadata, encoding); + } else if (type === 'clientStream') { + handleClientStreaming( + call, + handler as UntypedClientStreamingHandler, + metadata, + encoding + ); + } else if (type === 'serverStream') { + handleServerStreaming( + call, + handler as UntypedServerStreamingHandler, + metadata, + encoding + ); + } else if (type === 'bidi') { + handleBidiStreaming( + call, + handler as UntypedBidiStreamingHandler, + metadata, + encoding + ); + } else { + return false + } + + return true + } + + private _setupHandlers( + http2Server: http2.Http2Server | http2.Http2SecureServer + ): void { + if (http2Server === null) { + return; + } + + const serverAddress = http2Server.address(); + let serverAddressString = 'null' + if (serverAddress) { + if (typeof serverAddress === 'string') { + serverAddressString = serverAddress + } else { + serverAddressString = + serverAddress.address + ':' + serverAddress.port + } + } + this.serverAddressString = serverAddressString + + const handler = this.channelzEnabled + ? this._channelzHandler + : this._streamHandler + + http2Server.on('stream', handler.bind(this)) + http2Server.on('session', (session) => { + if (!this.started) { + session.destroy(); + return; + } + + let channelzRef: SocketRef; + channelzRef = registerChannelzSocket(session.socket.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session), this.channelzEnabled); + + const channelzSessionInfo: ChannelzSessionInfo = { + ref: channelzRef, + streamTracker: new ChannelzCallTracker(), + messagesSent: 0, + messagesReceived: 0, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null + }; + + this.sessions.set(session, channelzSessionInfo); + const clientAddress = session.socket.remoteAddress; + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); + this.sessionChildrenTracker.refChild(channelzRef); + } + let connectionAgeTimer: NodeJS.Timeout | null = null; + let connectionAgeGraceTimer: NodeJS.Timeout | null = null; + let sessionClosedByServer = false; + if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) { + // Apply a random jitter within a +/-10% range + const jitterMagnitude = this.maxConnectionAgeMs / 10; + const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude; + connectionAgeTimer = setTimeout(() => { + sessionClosedByServer = true; + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by max connection age from ' + clientAddress); + } + try { + session.goaway(http2.constants.NGHTTP2_NO_ERROR, ~(1<<31), Buffer.from('max_age')); + } catch (e) { + // The goaway can't be sent because the session is already closed + session.destroy(); + return; + } + session.close(); + /* Allow a grace period after sending the GOAWAY before forcibly + * closing the connection. */ + if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { + connectionAgeGraceTimer = setTimeout(() => { + session.destroy(); + }, this.maxConnectionAgeGraceMs).unref?.(); + } + }, this.maxConnectionAgeMs + jitter).unref?.(); + } + const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => { + const timeoutTImer = setTimeout(() => { + sessionClosedByServer = true; + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by keepalive timeout from ' + clientAddress); + } + session.close(); + }, this.keepaliveTimeoutMs).unref?.(); + try { + session.ping((err: Error | null, duration: number, payload: Buffer) => { + clearTimeout(timeoutTImer); + }); + } catch (e) { + // The ping can't be sent because the session is already closed + session.destroy(); + } + }, this.keepaliveTimeMs).unref?.(); + session.on('close', () => { + if (this.channelzEnabled) { + if (!sessionClosedByServer) { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); + } + this.sessionChildrenTracker.unrefChild(channelzRef); + unregisterChannelzRef(channelzRef); + } + if (connectionAgeTimer) { + clearTimeout(connectionAgeTimer); + } + if (connectionAgeGraceTimer) { + clearTimeout(connectionAgeGraceTimer); + } + if (keeapliveTimeTimer) { + clearTimeout(keeapliveTimeTimer); + } + this.sessions.delete(session); + }); + }); + } +} + +function handleUnary( + call: Http2ServerCallStream, + handler: UnaryHandler, + metadata: Metadata, + encoding: string +): void { + call.receiveUnaryMessage(encoding, (err, request) => { + if (err) { + call.sendError(err) + return + } + + if (request === undefined || call.cancelled) { + return; + } + + const emitter = new ServerUnaryCallImpl( + call, + metadata, + request + ); + + handler.func( + emitter, + ( + err: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + trailer?: Metadata, + flags?: number + ) => { + call.sendUnaryMessage(err, value, trailer, flags); + } + ); + }); +} + +function handleClientStreaming( + call: Http2ServerCallStream, + handler: ClientStreamingHandler, + metadata: Metadata, + encoding: string +): void { + const stream = new ServerReadableStreamImpl( + call, + metadata, + handler.deserialize, + encoding + ); + + function respond( + err: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + trailer?: Metadata, + flags?: number + ) { + stream.destroy(); + call.sendUnaryMessage(err, value, trailer, flags); + } + + if (call.cancelled) { + return; + } + + stream.on('error', respond); + handler.func(stream, respond); +} + +function handleServerStreaming( + call: Http2ServerCallStream, + handler: ServerStreamingHandler, + metadata: Metadata, + encoding: string +): void { + call.receiveUnaryMessage(encoding, (err, request) => { + if (err) { + call.sendError(err) + return + } + + if (request === undefined || call.cancelled) { + return; + } + + const stream = new ServerWritableStreamImpl( + call, + metadata, + handler.serialize, + request + ); + + handler.func(stream); + }); +} + +function handleBidiStreaming( + call: Http2ServerCallStream, + handler: BidiStreamingHandler, + metadata: Metadata, + encoding: string +): void { + const stream = new ServerDuplexStreamImpl( + call, + metadata, + handler.serialize, + handler.deserialize, + encoding + ); + + if (call.cancelled) { + return; + } + + handler.func(stream); +} diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts new file mode 100644 index 000000000..201c0c648 --- /dev/null +++ b/packages/grpc-js/src/service-config.ts @@ -0,0 +1,457 @@ +/* + * Copyright 2019 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. + * + */ + +/* This file implements gRFC A2 and the service config spec: + * https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md + * https://github.com/grpc/grpc/blob/master/doc/service_config.md. Each + * function here takes an object with unknown structure and returns its + * specific object type if the input has the right structure, and throws an + * error otherwise. */ + +/* The any type is purposely used here. All functions validate their input at + * runtime */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import * as os from 'os'; +import { Status } from './constants'; +import { Duration } from './duration'; +import { + LoadBalancingConfig, + validateLoadBalancingConfig, +} from './load-balancer'; + +export interface MethodConfigName { + service: string; + method?: string; +} + +export interface RetryPolicy { + maxAttempts: number; + initialBackoff: string; + maxBackoff: string; + backoffMultiplier: number; + retryableStatusCodes: (Status | string)[]; +} + +export interface HedgingPolicy { + maxAttempts: number; + hedgingDelay?: string; + nonFatalStatusCodes?: (Status | string)[]; +} + +export interface MethodConfig { + name: MethodConfigName[]; + waitForReady?: boolean; + timeout?: Duration; + maxRequestBytes?: number; + maxResponseBytes?: number; + retryPolicy?: RetryPolicy; + hedgingPolicy?: HedgingPolicy; +} + +export interface RetryThrottling { + maxTokens: number; + tokenRatio: number; +} + +export interface ServiceConfig { + loadBalancingPolicy?: string; + loadBalancingConfig: LoadBalancingConfig[]; + methodConfig: MethodConfig[]; + retryThrottling?: RetryThrottling; +} + +export interface ServiceConfigCanaryConfig { + clientLanguage?: string[]; + percentage?: number; + clientHostname?: string[]; + serviceConfig: ServiceConfig; +} + +/** + * Recognizes a number with up to 9 digits after the decimal point, followed by + * an "s", representing a number of seconds. + */ +const DURATION_REGEX = /^\d+(\.\d{1,9})?s$/; + +/** + * Client language name used for determining whether this client matches a + * `ServiceConfigCanaryConfig`'s `clientLanguage` list. + */ +const CLIENT_LANGUAGE_STRING = 'node'; + +function validateName(obj: any): MethodConfigName { + if (!('service' in obj) || typeof obj.service !== 'string') { + throw new Error('Invalid method config name: invalid service'); + } + const result: MethodConfigName = { + service: obj.service, + }; + if ('method' in obj) { + if (typeof obj.method === 'string') { + result.method = obj.method; + } else { + throw new Error('Invalid method config name: invalid method'); + } + } + return result; +} + +function validateRetryPolicy(obj: any): RetryPolicy { + if (!('maxAttempts' in obj) || !Number.isInteger(obj.maxAttempts) || obj.maxAttempts < 2) { + throw new Error('Invalid method config retry policy: maxAttempts must be an integer at least 2'); + } + if (!('initialBackoff' in obj) || typeof obj.initialBackoff !== 'string' || !DURATION_REGEX.test(obj.initialBackoff)) { + throw new Error('Invalid method config retry policy: initialBackoff must be a string consisting of a positive integer followed by s'); + } + if (!('maxBackoff' in obj) || typeof obj.maxBackoff !== 'string' || !DURATION_REGEX.test(obj.maxBackoff)) { + throw new Error('Invalid method config retry policy: maxBackoff must be a string consisting of a positive integer followed by s'); + } + if (!('backoffMultiplier' in obj) || typeof obj.backoffMultiplier !== 'number' || obj.backoffMultiplier <= 0) { + throw new Error('Invalid method config retry policy: backoffMultiplier must be a number greater than 0'); + } + if (!(('retryableStatusCodes' in obj) && Array.isArray(obj.retryableStatusCodes))) { + throw new Error('Invalid method config retry policy: retryableStatusCodes is required'); + } + if (obj.retryableStatusCodes.length === 0) { + throw new Error('Invalid method config retry policy: retryableStatusCodes must be non-empty'); + } + for (const value of obj.retryableStatusCodes) { + if (typeof value === 'number') { + if (!Object.values(Status).includes(value)) { + throw new Error('Invalid method config retry policy: retryableStatusCodes value not in status code range'); + } + } else if (typeof value === 'string') { + if (!Object.values(Status).includes(value.toUpperCase())) { + throw new Error('Invalid method config retry policy: retryableStatusCodes value not a status code name'); + } + } else { + throw new Error('Invalid method config retry policy: retryableStatusCodes value must be a string or number'); + } + } + return { + maxAttempts: obj.maxAttempts, + initialBackoff: obj.initialBackoff, + maxBackoff: obj.maxBackoff, + backoffMultiplier: obj.backoffMultiplier, + retryableStatusCodes: obj.retryableStatusCodes + }; +} + +function validateHedgingPolicy(obj: any): HedgingPolicy { + if (!('maxAttempts' in obj) || !Number.isInteger(obj.maxAttempts) || obj.maxAttempts < 2) { + throw new Error('Invalid method config hedging policy: maxAttempts must be an integer at least 2'); + } + if (('hedgingDelay' in obj) && (typeof obj.hedgingDelay !== 'string' || !DURATION_REGEX.test(obj.hedgingDelay))) { + throw new Error('Invalid method config hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s'); + } + if (('nonFatalStatusCodes' in obj) && Array.isArray(obj.nonFatalStatusCodes)) { + for (const value of obj.nonFatalStatusCodes) { + if (typeof value === 'number') { + if (!Object.values(Status).includes(value)) { + throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value not in status code range'); + } + } else if (typeof value === 'string') { + if (!Object.values(Status).includes(value.toUpperCase())) { + throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value not a status code name'); + } + } else { + throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value must be a string or number'); + } + } + } + const result: HedgingPolicy = { + maxAttempts: obj.maxAttempts + } + if (obj.hedgingDelay) { + result.hedgingDelay = obj.hedgingDelay; + } + if (obj.nonFatalStatusCodes) { + result.nonFatalStatusCodes = obj.nonFatalStatusCodes; + } + return result; +} + +function validateMethodConfig(obj: any): MethodConfig { + const result: MethodConfig = { + name: [], + }; + if (!('name' in obj) || !Array.isArray(obj.name)) { + throw new Error('Invalid method config: invalid name array'); + } + for (const name of obj.name) { + result.name.push(validateName(name)); + } + if ('waitForReady' in obj) { + if (typeof obj.waitForReady !== 'boolean') { + throw new Error('Invalid method config: invalid waitForReady'); + } + result.waitForReady = obj.waitForReady; + } + if ('timeout' in obj) { + if (typeof obj.timeout === 'object') { + if ( + !('seconds' in obj.timeout) || + !(typeof obj.timeout.seconds === 'number') + ) { + throw new Error('Invalid method config: invalid timeout.seconds'); + } + if ( + !('nanos' in obj.timeout) || + !(typeof obj.timeout.nanos === 'number') + ) { + throw new Error('Invalid method config: invalid timeout.nanos'); + } + result.timeout = obj.timeout; + } else if ( + typeof obj.timeout === 'string' && + DURATION_REGEX.test(obj.timeout) + ) { + const timeoutParts = obj.timeout + .substring(0, obj.timeout.length - 1) + .split('.'); + result.timeout = { + seconds: timeoutParts[0] | 0, + nanos: (timeoutParts[1] ?? 0) | 0, + }; + } else { + throw new Error('Invalid method config: invalid timeout'); + } + } + if ('maxRequestBytes' in obj) { + if (typeof obj.maxRequestBytes !== 'number') { + throw new Error('Invalid method config: invalid maxRequestBytes'); + } + result.maxRequestBytes = obj.maxRequestBytes; + } + if ('maxResponseBytes' in obj) { + if (typeof obj.maxResponseBytes !== 'number') { + throw new Error('Invalid method config: invalid maxRequestBytes'); + } + result.maxResponseBytes = obj.maxResponseBytes; + } + if ('retryPolicy' in obj) { + if ('hedgingPolicy' in obj) { + throw new Error('Invalid method config: retryPolicy and hedgingPolicy cannot both be specified'); + } else { + result.retryPolicy = validateRetryPolicy(obj.retryPolicy); + } + } else if ('hedgingPolicy' in obj) { + result.hedgingPolicy = validateHedgingPolicy(obj.hedgingPolicy); + } + return result; +} + +export function validateRetryThrottling(obj: any): RetryThrottling { + if (!('maxTokens' in obj) || typeof obj.maxTokens !== 'number' || obj.maxTokens <=0 || obj.maxTokens > 1000) { + throw new Error('Invalid retryThrottling: maxTokens must be a number in (0, 1000]'); + } + if (!('tokenRatio' in obj) || typeof obj.tokenRatio !== 'number' || obj.tokenRatio <= 0) { + throw new Error('Invalid retryThrottling: tokenRatio must be a number greater than 0'); + } + return { + maxTokens: +(obj.maxTokens as number).toFixed(3), + tokenRatio: +(obj.tokenRatio as number).toFixed(3) + }; +} + +export function validateServiceConfig(obj: any): ServiceConfig { + const result: ServiceConfig = { + loadBalancingConfig: [], + methodConfig: [], + }; + if ('loadBalancingPolicy' in obj) { + if (typeof obj.loadBalancingPolicy === 'string') { + result.loadBalancingPolicy = obj.loadBalancingPolicy; + } else { + throw new Error('Invalid service config: invalid loadBalancingPolicy'); + } + } + if ('loadBalancingConfig' in obj) { + if (Array.isArray(obj.loadBalancingConfig)) { + for (const config of obj.loadBalancingConfig) { + result.loadBalancingConfig.push(validateLoadBalancingConfig(config)); + } + } else { + throw new Error('Invalid service config: invalid loadBalancingConfig'); + } + } + if ('methodConfig' in obj) { + if (Array.isArray(obj.methodConfig)) { + for (const methodConfig of obj.methodConfig) { + result.methodConfig.push(validateMethodConfig(methodConfig)); + } + } + } + if ('retryThrottling' in obj) { + result.retryThrottling = validateRetryThrottling(obj.retryThrottling); + } + // Validate method name uniqueness + const seenMethodNames: MethodConfigName[] = []; + for (const methodConfig of result.methodConfig) { + for (const name of methodConfig.name) { + for (const seenName of seenMethodNames) { + if ( + name.service === seenName.service && + name.method === seenName.method + ) { + throw new Error( + `Invalid service config: duplicate name ${name.service}/${name.method}` + ); + } + } + seenMethodNames.push(name); + } + } + return result; +} + +function validateCanaryConfig(obj: any): ServiceConfigCanaryConfig { + if (!('serviceConfig' in obj)) { + throw new Error('Invalid service config choice: missing service config'); + } + const result: ServiceConfigCanaryConfig = { + serviceConfig: validateServiceConfig(obj.serviceConfig), + }; + if ('clientLanguage' in obj) { + if (Array.isArray(obj.clientLanguage)) { + result.clientLanguage = []; + for (const lang of obj.clientLanguage) { + if (typeof lang === 'string') { + result.clientLanguage.push(lang); + } else { + throw new Error( + 'Invalid service config choice: invalid clientLanguage' + ); + } + } + } else { + throw new Error('Invalid service config choice: invalid clientLanguage'); + } + } + if ('clientHostname' in obj) { + if (Array.isArray(obj.clientHostname)) { + result.clientHostname = []; + for (const lang of obj.clientHostname) { + if (typeof lang === 'string') { + result.clientHostname.push(lang); + } else { + throw new Error( + 'Invalid service config choice: invalid clientHostname' + ); + } + } + } else { + throw new Error('Invalid service config choice: invalid clientHostname'); + } + } + if ('percentage' in obj) { + if ( + typeof obj.percentage === 'number' && + 0 <= obj.percentage && + obj.percentage <= 100 + ) { + result.percentage = obj.percentage; + } else { + throw new Error('Invalid service config choice: invalid percentage'); + } + } + // Validate that no unexpected fields are present + const allowedFields = [ + 'clientLanguage', + 'percentage', + 'clientHostname', + 'serviceConfig', + ]; + for (const field in obj) { + if (!allowedFields.includes(field)) { + throw new Error( + `Invalid service config choice: unexpected field ${field}` + ); + } + } + return result; +} + +function validateAndSelectCanaryConfig( + obj: any, + percentage: number +): ServiceConfig { + if (!Array.isArray(obj)) { + throw new Error('Invalid service config list'); + } + for (const config of obj) { + const validatedConfig = validateCanaryConfig(config); + /* For each field, we check if it is present, then only discard the + * config if the field value does not match the current client */ + if ( + typeof validatedConfig.percentage === 'number' && + percentage > validatedConfig.percentage + ) { + continue; + } + if (Array.isArray(validatedConfig.clientHostname)) { + let hostnameMatched = false; + for (const hostname of validatedConfig.clientHostname) { + if (hostname === os.hostname()) { + hostnameMatched = true; + } + } + if (!hostnameMatched) { + continue; + } + } + if (Array.isArray(validatedConfig.clientLanguage)) { + let languageMatched = false; + for (const language of validatedConfig.clientLanguage) { + if (language === CLIENT_LANGUAGE_STRING) { + languageMatched = true; + } + } + if (!languageMatched) { + continue; + } + } + return validatedConfig.serviceConfig; + } + throw new Error('No matching service config found'); +} + +/** + * Find the "grpc_config" record among the TXT records, parse its value as JSON, validate its contents, + * and select a service config with selection fields that all match this client. Most of these steps + * can fail with an error; the caller must handle any errors thrown this way. + * @param txtRecord The TXT record array that is output from a successful call to dns.resolveTxt + * @param percentage A number chosen from the range [0, 100) that is used to select which config to use + * @return The service configuration to use, given the percentage value, or null if the service config + * data has a valid format but none of the options match the current client. + */ +export function extractAndSelectServiceConfig( + txtRecord: string[][], + percentage: number +): ServiceConfig | null { + for (const record of txtRecord) { + if (record.length > 0 && record[0].startsWith('grpc_config=')) { + /* Treat the list of strings in this record as a single string and remove + * "grpc_config=" from the beginning. The rest should be a JSON string */ + const recordString = record.join('').substring('grpc_config='.length); + const recordJson: any = JSON.parse(recordString); + return validateAndSelectCanaryConfig(recordJson, percentage); + } + } + return null; +} diff --git a/packages/grpc-js/src/status-builder.ts b/packages/grpc-js/src/status-builder.ts new file mode 100644 index 000000000..78e2ea310 --- /dev/null +++ b/packages/grpc-js/src/status-builder.ts @@ -0,0 +1,80 @@ +/* + * Copyright 2019 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. + * + */ + +import { StatusObject } from './call-interface'; +import { Status } from './constants'; +import { Metadata } from './metadata'; + +/** + * A builder for gRPC status objects. + */ +export class StatusBuilder { + private code: Status | null; + private details: string | null; + private metadata: Metadata | null; + + constructor() { + this.code = null; + this.details = null; + this.metadata = null; + } + + /** + * Adds a status code to the builder. + */ + withCode(code: Status): this { + this.code = code; + return this; + } + + /** + * Adds details to the builder. + */ + withDetails(details: string): this { + this.details = details; + return this; + } + + /** + * Adds metadata to the builder. + */ + withMetadata(metadata: Metadata): this { + this.metadata = metadata; + return this; + } + + /** + * Builds the status object. + */ + build(): Partial { + const status: Partial = {}; + + if (this.code !== null) { + status.code = this.code; + } + + if (this.details !== null) { + status.details = this.details; + } + + if (this.metadata !== null) { + status.metadata = this.metadata; + } + + return status; + } +} diff --git a/packages/grpc-js/src/stream-decoder.ts b/packages/grpc-js/src/stream-decoder.ts new file mode 100644 index 000000000..ea669d14c --- /dev/null +++ b/packages/grpc-js/src/stream-decoder.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2019 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. + * + */ + +enum ReadState { + NO_DATA, + READING_SIZE, + READING_MESSAGE, +} + +export class StreamDecoder { + private readState: ReadState = ReadState.NO_DATA; + private readCompressFlag: Buffer = Buffer.alloc(1); + private readPartialSize: Buffer = Buffer.alloc(4); + private readSizeRemaining = 4; + private readMessageSize = 0; + private readPartialMessage: Buffer[] = []; + private readMessageRemaining = 0; + + constructor(private maxReadMessageLength: number) {} + + write(data: Buffer): Buffer[] { + let readHead = 0; + let toRead: number; + const result: Buffer[] = []; + + while (readHead < data.length) { + switch (this.readState) { + case ReadState.NO_DATA: + this.readCompressFlag = data.slice(readHead, readHead + 1); + readHead += 1; + this.readState = ReadState.READING_SIZE; + this.readPartialSize.fill(0); + this.readSizeRemaining = 4; + this.readMessageSize = 0; + this.readMessageRemaining = 0; + this.readPartialMessage = []; + break; + case ReadState.READING_SIZE: + toRead = Math.min(data.length - readHead, this.readSizeRemaining); + data.copy( + this.readPartialSize, + 4 - this.readSizeRemaining, + readHead, + readHead + toRead + ); + this.readSizeRemaining -= toRead; + readHead += toRead; + // readSizeRemaining >=0 here + if (this.readSizeRemaining === 0) { + this.readMessageSize = this.readPartialSize.readUInt32BE(0); + if (this.maxReadMessageLength !== -1 && this.readMessageSize > this.maxReadMessageLength) { + throw new Error(`Received message larger than max (${this.readMessageSize} vs ${this.maxReadMessageLength})`); + } + this.readMessageRemaining = this.readMessageSize; + if (this.readMessageRemaining > 0) { + this.readState = ReadState.READING_MESSAGE; + } else { + const message = Buffer.concat( + [this.readCompressFlag, this.readPartialSize], + 5 + ); + + this.readState = ReadState.NO_DATA; + result.push(message); + } + } + break; + case ReadState.READING_MESSAGE: + toRead = Math.min(data.length - readHead, this.readMessageRemaining); + this.readPartialMessage.push(data.slice(readHead, readHead + toRead)); + this.readMessageRemaining -= toRead; + readHead += toRead; + // readMessageRemaining >=0 here + if (this.readMessageRemaining === 0) { + // At this point, we have read a full message + const framedMessageBuffers = [ + this.readCompressFlag, + this.readPartialSize, + ].concat(this.readPartialMessage); + const framedMessage = Buffer.concat( + framedMessageBuffers, + this.readMessageSize + 5 + ); + + this.readState = ReadState.NO_DATA; + result.push(framedMessage); + } + break; + default: + throw new Error('Unexpected read state'); + } + } + + return result; + } +} diff --git a/packages/grpc-js/src/subchannel-address.ts b/packages/grpc-js/src/subchannel-address.ts new file mode 100644 index 000000000..e542e645e --- /dev/null +++ b/packages/grpc-js/src/subchannel-address.ts @@ -0,0 +1,85 @@ +/* + * Copyright 2021 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. + * + */ + +import { isIP } from "net"; + +export interface TcpSubchannelAddress { + port: number; + host: string; +} + +export interface IpcSubchannelAddress { + path: string; +} +/** + * This represents a single backend address to connect to. This interface is a + * subset of net.SocketConnectOpts, i.e. the options described at + * https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener. + * Those are in turn a subset of the options that can be passed to http2.connect. + */ + +export type SubchannelAddress = TcpSubchannelAddress | IpcSubchannelAddress; + +export function isTcpSubchannelAddress( + address: SubchannelAddress +): address is TcpSubchannelAddress { + return 'port' in address; +} + +export function subchannelAddressEqual( + address1?: SubchannelAddress, + address2?: SubchannelAddress +): boolean { + if (!address1 && !address2) { + return true; + } + if (!address1 || !address2) { + return false; + } + if (isTcpSubchannelAddress(address1)) { + return ( + isTcpSubchannelAddress(address2) && + address1.host === address2.host && + address1.port === address2.port + ); + } else { + return !isTcpSubchannelAddress(address2) && address1.path === address2.path; + } +} + +export function subchannelAddressToString(address: SubchannelAddress): string { + if (isTcpSubchannelAddress(address)) { + return address.host + ':' + address.port; + } else { + return address.path; + } +} + +const DEFAULT_PORT = 443; + +export function stringToSubchannelAddress(addressString: string, port?: number): SubchannelAddress { + if (isIP(addressString)) { + return { + host: addressString, + port: port ?? DEFAULT_PORT + }; + } else { + return { + path: addressString + }; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts new file mode 100644 index 000000000..a210c9ce7 --- /dev/null +++ b/packages/grpc-js/src/subchannel-call.ts @@ -0,0 +1,525 @@ +/* + * Copyright 2019 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. + * + */ + +import * as http2 from 'http2'; +import * as os from 'os'; + +import { DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, Status } from './constants'; +import { Metadata } from './metadata'; +import { StreamDecoder } from './stream-decoder'; +import * as logging from './logging'; +import { LogVerbosity } from './constants'; +import { InterceptingListener, MessageContext, StatusObject, WriteCallback } from './call-interface'; +import { CallEventTracker, Transport } from './transport'; + +const TRACER_NAME = 'subchannel_call'; + +/** + * https://nodejs.org/api/errors.html#errors_class_systemerror + */ +interface SystemError extends Error { + address?: string; + code: string; + dest?: string; + errno: number; + info?: object; + message: string; + path?: string; + port?: number; + syscall: string; +} + +/** + * Should do approximately the same thing as util.getSystemErrorName but the + * TypeScript types don't have that function for some reason so I just made my + * own. + * @param errno + */ +function getSystemErrorName(errno: number): string { + for (const [name, num] of Object.entries(os.constants.errno)) { + if (num === errno) { + return name; + } + } + return 'Unknown system error ' + errno; +} + +export interface SubchannelCall { + cancelWithStatus(status: Status, details: string): void; + getPeer(): string; + sendMessageWithContext(context: MessageContext, message: Buffer): void; + startRead(): void; + halfClose(): void; + getCallNumber(): number; +} + +export interface StatusObjectWithRstCode extends StatusObject { + rstCode?: number; +} + +export interface SubchannelCallInterceptingListener extends InterceptingListener { + onReceiveStatus(status: StatusObjectWithRstCode): void; +} + +export class Http2SubchannelCall implements SubchannelCall { + private decoder: StreamDecoder; + + private isReadFilterPending = false; + private isPushPending = false; + private canPush = false; + /** + * Indicates that an 'end' event has come from the http2 stream, so there + * will be no more data events. + */ + private readsClosed = false; + + private statusOutput = false; + + private unpushedReadMessages: Buffer[] = []; + + // Status code mapped from :status. To be used if grpc-status is not received + private mappedStatusCode: Status = Status.UNKNOWN; + + // This is populated (non-null) if and only if the call has ended + private finalStatus: StatusObject | null = null; + + private internalError: SystemError | null = null; + + constructor( + private readonly http2Stream: http2.ClientHttp2Stream, + private readonly callEventTracker: CallEventTracker, + private readonly listener: SubchannelCallInterceptingListener, + private readonly transport: Transport, + private readonly callId: number + ) { + const maxReceiveMessageLength = transport.getOptions()['grpc.max_receive_message_length'] ?? DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; + this.decoder = new StreamDecoder(maxReceiveMessageLength); + http2Stream.on('response', (headers, flags) => { + let headersString = ''; + for (const header of Object.keys(headers)) { + headersString += '\t\t' + header + ': ' + headers[header] + '\n'; + } + this.trace('Received server headers:\n' + headersString); + switch (headers[':status']) { + // TODO(murgatroid99): handle 100 and 101 + case 400: + this.mappedStatusCode = Status.INTERNAL; + break; + case 401: + this.mappedStatusCode = Status.UNAUTHENTICATED; + break; + case 403: + this.mappedStatusCode = Status.PERMISSION_DENIED; + break; + case 404: + this.mappedStatusCode = Status.UNIMPLEMENTED; + break; + case 429: + case 502: + case 503: + case 504: + this.mappedStatusCode = Status.UNAVAILABLE; + break; + default: + this.mappedStatusCode = Status.UNKNOWN; + } + + if (flags & http2.constants.NGHTTP2_FLAG_END_STREAM) { + this.handleTrailers(headers); + } else { + let metadata: Metadata; + try { + metadata = Metadata.fromHttp2Headers(headers); + } catch (error) { + this.endCall({ + code: Status.UNKNOWN, + details: (error as Error).message, + metadata: new Metadata(), + }); + return; + } + this.listener.onReceiveMetadata(metadata); + } + }); + http2Stream.on('trailers', (headers: http2.IncomingHttpHeaders) => { + this.handleTrailers(headers); + }); + http2Stream.on('data', (data: Buffer) => { + /* If the status has already been output, allow the http2 stream to + * drain without processing the data. */ + if (this.statusOutput) { + return; + } + this.trace('receive HTTP/2 data frame of length ' + data.length); + let messages: Buffer[]; + try { + messages = this.decoder.write(data); + } catch (e) { + this.cancelWithStatus(Status.RESOURCE_EXHAUSTED, (e as Error).message); + return; + } + + for (const message of messages) { + this.trace('parsed message of length ' + message.length); + this.callEventTracker!.addMessageReceived(); + this.tryPush(message); + } + }); + http2Stream.on('end', () => { + this.readsClosed = true; + this.maybeOutputStatus(); + }); + http2Stream.on('close', () => { + /* Use process.next tick to ensure that this code happens after any + * "error" event that may be emitted at about the same time, so that + * we can bubble up the error message from that event. */ + process.nextTick(() => { + this.trace('HTTP/2 stream closed with code ' + http2Stream.rstCode); + /* If we have a final status with an OK status code, that means that + * we have received all of the messages and we have processed the + * trailers and the call completed successfully, so it doesn't matter + * how the stream ends after that */ + if (this.finalStatus?.code === Status.OK) { + return; + } + let code: Status; + let details = ''; + switch (http2Stream.rstCode) { + case http2.constants.NGHTTP2_NO_ERROR: + /* If we get a NO_ERROR code and we already have a status, the + * stream completed properly and we just haven't fully processed + * it yet */ + if (this.finalStatus !== null) { + return; + } + code = Status.INTERNAL; + details = `Received RST_STREAM with code ${http2Stream.rstCode}`; + break; + case http2.constants.NGHTTP2_REFUSED_STREAM: + code = Status.UNAVAILABLE; + details = 'Stream refused by server'; + break; + case http2.constants.NGHTTP2_CANCEL: + code = Status.CANCELLED; + details = 'Call cancelled'; + break; + case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM: + code = Status.RESOURCE_EXHAUSTED; + details = 'Bandwidth exhausted or memory limit exceeded'; + break; + case http2.constants.NGHTTP2_INADEQUATE_SECURITY: + code = Status.PERMISSION_DENIED; + details = 'Protocol not secure enough'; + break; + case http2.constants.NGHTTP2_INTERNAL_ERROR: + code = Status.INTERNAL; + if (this.internalError === null) { + /* This error code was previously handled in the default case, and + * there are several instances of it online, so I wanted to + * preserve the original error message so that people find existing + * information in searches, but also include the more recognizable + * "Internal server error" message. */ + details = `Received RST_STREAM with code ${http2Stream.rstCode} (Internal server error)`; + } else { + if (this.internalError.code === 'ECONNRESET' || this.internalError.code === 'ETIMEDOUT') { + code = Status.UNAVAILABLE; + details = this.internalError.message; + } else { + /* The "Received RST_STREAM with code ..." error is preserved + * here for continuity with errors reported online, but the + * error message at the end will probably be more relevant in + * most cases. */ + details = `Received RST_STREAM with code ${http2Stream.rstCode} triggered by internal client error: ${this.internalError.message}`; + } + } + break; + default: + code = Status.INTERNAL; + details = `Received RST_STREAM with code ${http2Stream.rstCode}`; + } + // This is a no-op if trailers were received at all. + // This is OK, because status codes emitted here correspond to more + // catastrophic issues that prevent us from receiving trailers in the + // first place. + this.endCall({ code, details, metadata: new Metadata(), rstCode: http2Stream.rstCode }); + }); + }); + http2Stream.on('error', (err: SystemError) => { + /* We need an error handler here to stop "Uncaught Error" exceptions + * from bubbling up. However, errors here should all correspond to + * "close" events, where we will handle the error more granularly */ + /* Specifically looking for stream errors that were *not* constructed + * from a RST_STREAM response here: + * https://github.com/nodejs/node/blob/8b8620d580314050175983402dfddf2674e8e22a/lib/internal/http2/core.js#L2267 + */ + if (err.code !== 'ERR_HTTP2_STREAM_ERROR') { + this.trace( + 'Node error event: message=' + + err.message + + ' code=' + + err.code + + ' errno=' + + getSystemErrorName(err.errno) + + ' syscall=' + + err.syscall + ); + this.internalError = err; + } + this.callEventTracker.onStreamEnd(false); + }); + } + + public onDisconnect() { + this.endCall({ + code: Status.UNAVAILABLE, + details: 'Connection dropped', + metadata: new Metadata(), + }); + } + + private outputStatus() { + /* Precondition: this.finalStatus !== null */ + if (!this.statusOutput) { + this.statusOutput = true; + this.trace( + 'ended with status: code=' + + this.finalStatus!.code + + ' details="' + + this.finalStatus!.details + + '"' + ); + this.callEventTracker.onCallEnd(this.finalStatus!); + /* We delay the actual action of bubbling up the status to insulate the + * cleanup code in this class from any errors that may be thrown in the + * upper layers as a result of bubbling up the status. In particular, + * if the status is not OK, the "error" event may be emitted + * synchronously at the top level, which will result in a thrown error if + * the user does not handle that event. */ + process.nextTick(() => { + this.listener.onReceiveStatus(this.finalStatus!); + }); + /* Leave the http2 stream in flowing state to drain incoming messages, to + * ensure that the stream closure completes. The call stream already does + * not push more messages after the status is output, so the messages go + * nowhere either way. */ + this.http2Stream.resume(); + } + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callId + '] ' + text + ); + } + + /** + * On first call, emits a 'status' event with the given StatusObject. + * Subsequent calls are no-ops. + * @param status The status of the call. + */ + private endCall(status: StatusObjectWithRstCode): void { + /* If the status is OK and a new status comes in (e.g. from a + * deserialization failure), that new status takes priority */ + if (this.finalStatus === null || this.finalStatus.code === Status.OK) { + this.finalStatus = status; + this.maybeOutputStatus(); + } + this.destroyHttp2Stream(); + } + + private maybeOutputStatus() { + if (this.finalStatus !== null) { + /* The combination check of readsClosed and that the two message buffer + * arrays are empty checks that there all incoming data has been fully + * processed */ + if ( + this.finalStatus.code !== Status.OK || + (this.readsClosed && + this.unpushedReadMessages.length === 0 && + !this.isReadFilterPending && + !this.isPushPending) + ) { + this.outputStatus(); + } + } + } + + private push(message: Buffer): void { + this.trace( + 'pushing to reader message of length ' + + (message instanceof Buffer ? message.length : null) + ); + this.canPush = false; + this.isPushPending = true; + process.nextTick(() => { + this.isPushPending = false; + /* If we have already output the status any later messages should be + * ignored, and can cause out-of-order operation errors higher up in the + * stack. Checking as late as possible here to avoid any race conditions. + */ + if (this.statusOutput) { + return; + } + this.listener.onReceiveMessage(message); + this.maybeOutputStatus(); + }); + } + + private tryPush(messageBytes: Buffer): void { + if (this.canPush) { + this.http2Stream!.pause(); + this.push(messageBytes); + } else { + this.trace( + 'unpushedReadMessages.push message of length ' + messageBytes.length + ); + this.unpushedReadMessages.push(messageBytes); + } + } + + private handleTrailers(headers: http2.IncomingHttpHeaders) { + this.callEventTracker.onStreamEnd(true); + let headersString = ''; + for (const header of Object.keys(headers)) { + headersString += '\t\t' + header + ': ' + headers[header] + '\n'; + } + this.trace('Received server trailers:\n' + headersString); + let metadata: Metadata; + try { + metadata = Metadata.fromHttp2Headers(headers); + } catch (e) { + metadata = new Metadata(); + } + const metadataMap = metadata.getMap(); + let code: Status = this.mappedStatusCode; + if ( + code === Status.UNKNOWN && + typeof metadataMap['grpc-status'] === 'string' + ) { + const receivedStatus = Number(metadataMap['grpc-status']); + if (receivedStatus in Status) { + code = receivedStatus; + this.trace('received status code ' + receivedStatus + ' from server'); + } + metadata.remove('grpc-status'); + } + let details = ''; + if (typeof metadataMap['grpc-message'] === 'string') { + try { + details = decodeURI(metadataMap['grpc-message']); + } catch (e) { + details = metadataMap['grpc-message']; + } + metadata.remove('grpc-message'); + this.trace( + 'received status details string "' + details + '" from server' + ); + } + const status: StatusObject = { code, details, metadata }; + // This is a no-op if the call was already ended when handling headers. + this.endCall(status); + } + + private destroyHttp2Stream() { + // The http2 stream could already have been destroyed if cancelWithStatus + // is called in response to an internal http2 error. + if (!this.http2Stream.destroyed) { + /* If the call has ended with an OK status, communicate that when closing + * the stream, partly to avoid a situation in which we detect an error + * RST_STREAM as a result after we have the status */ + let code: number; + if (this.finalStatus?.code === Status.OK) { + code = http2.constants.NGHTTP2_NO_ERROR; + } else { + code = http2.constants.NGHTTP2_CANCEL; + } + this.trace('close http2 stream with code ' + code); + this.http2Stream.close(code); + } + } + + cancelWithStatus(status: Status, details: string): void { + this.trace( + 'cancelWithStatus code: ' + status + ' details: "' + details + '"' + ); + this.endCall({ code: status, details, metadata: new Metadata() }); + } + + getStatus(): StatusObject | null { + return this.finalStatus; + } + + getPeer(): string { + return this.transport.getPeerName(); + } + + getCallNumber(): number { + return this.callId; + } + + startRead() { + /* If the stream has ended with an error, we should not emit any more + * messages and we should communicate that the stream has ended */ + if (this.finalStatus !== null && this.finalStatus.code !== Status.OK) { + this.readsClosed = true; + this.maybeOutputStatus(); + return; + } + this.canPush = true; + if (this.unpushedReadMessages.length > 0) { + const nextMessage: Buffer = this.unpushedReadMessages.shift()!; + this.push(nextMessage); + return; + } + /* Only resume reading from the http2Stream if we don't have any pending + * messages to emit */ + this.http2Stream.resume(); + } + + sendMessageWithContext(context: MessageContext, message: Buffer) { + this.trace('write() called with message of length ' + message.length); + const cb: WriteCallback = (error?: Error | null) => { + let code: Status = Status.UNAVAILABLE; + if ((error as NodeJS.ErrnoException)?.code === 'ERR_STREAM_WRITE_AFTER_END') { + code = Status.INTERNAL; + } + if (error) { + this.cancelWithStatus(code, `Write error: ${error.message}`); + } + context.callback?.(); + }; + this.trace('sending data chunk of length ' + message.length); + this.callEventTracker.addMessageSent(); + try { + this.http2Stream!.write(message, cb); + } catch (error) { + this.endCall({ + code: Status.UNAVAILABLE, + details: `Write failed with error ${(error as Error).message}`, + metadata: new Metadata() + }); + } + } + + halfClose() { + this.trace('end() called'); + this.trace('calling end() on HTTP/2 stream'); + this.http2Stream.end(); + } +} diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts new file mode 100644 index 000000000..165ebc3e1 --- /dev/null +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2022 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. + * + */ + +import { SubchannelRef } from "./channelz"; +import { ConnectivityState } from "./connectivity-state"; +import { Subchannel } from "./subchannel"; + +export type ConnectivityStateListener = ( + subchannel: SubchannelInterface, + previousState: ConnectivityState, + newState: ConnectivityState, + keepaliveTime: number +) => void; + +/** + * This is an interface for load balancing policies to use to interact with + * subchannels. This allows load balancing policies to wrap and unwrap + * subchannels. + * + * Any load balancing policy that wraps subchannels must unwrap the subchannel + * in the picker, so that other load balancing policies consistently have + * access to their own wrapper objects. + */ +export interface SubchannelInterface { + getConnectivityState(): ConnectivityState; + addConnectivityStateListener(listener: ConnectivityStateListener): void; + removeConnectivityStateListener(listener: ConnectivityStateListener): void; + startConnecting(): void; + getAddress(): string; + throttleKeepalive(newKeepaliveTime: number): void; + ref(): void; + unref(): void; + getChannelzRef(): SubchannelRef; + /** + * If this is a wrapper, return the wrapped subchannel, otherwise return this + */ + getRealSubchannel(): Subchannel; +} + +export abstract class BaseSubchannelWrapper implements SubchannelInterface { + constructor(protected child: SubchannelInterface) {} + + getConnectivityState(): ConnectivityState { + return this.child.getConnectivityState(); + } + addConnectivityStateListener(listener: ConnectivityStateListener): void { + this.child.addConnectivityStateListener(listener); + } + removeConnectivityStateListener(listener: ConnectivityStateListener): void { + this.child.removeConnectivityStateListener(listener); + } + startConnecting(): void { + this.child.startConnecting(); + } + getAddress(): string { + return this.child.getAddress(); + } + throttleKeepalive(newKeepaliveTime: number): void { + this.child.throttleKeepalive(newKeepaliveTime); + } + ref(): void { + this.child.ref(); + } + unref(): void { + this.child.unref(); + } + getChannelzRef(): SubchannelRef { + return this.child.getChannelzRef(); + } + getRealSubchannel(): Subchannel { + return this.child.getRealSubchannel(); + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/subchannel-pool.ts b/packages/grpc-js/src/subchannel-pool.ts new file mode 100644 index 000000000..f9400bede --- /dev/null +++ b/packages/grpc-js/src/subchannel-pool.ts @@ -0,0 +1,176 @@ +/* + * Copyright 2019 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. + * + */ + +import { ChannelOptions, channelOptionsEqual } from './channel-options'; +import { Subchannel } from './subchannel'; +import { + SubchannelAddress, + subchannelAddressEqual, +} from './subchannel-address'; +import { ChannelCredentials } from './channel-credentials'; +import { GrpcUri, uriToString } from './uri-parser'; +import { Http2SubchannelConnector } from './transport'; + +// 10 seconds in milliseconds. This value is arbitrary. +/** + * The amount of time in between checks for dropping subchannels that have no + * other references + */ +const REF_CHECK_INTERVAL = 10_000; + +export class SubchannelPool { + private pool: { + [channelTarget: string]: Array<{ + subchannelAddress: SubchannelAddress; + channelArguments: ChannelOptions; + channelCredentials: ChannelCredentials; + subchannel: Subchannel; + }>; + } = Object.create(null); + + /** + * A timer of a task performing a periodic subchannel cleanup. + */ + private cleanupTimer: NodeJS.Timeout | null = null; + + /** + * A pool of subchannels use for making connections. Subchannels with the + * exact same parameters will be reused. + */ + constructor() {} + + /** + * Unrefs all unused subchannels and cancels the cleanup task if all + * subchannels have been unrefed. + */ + unrefUnusedSubchannels(): void { + let allSubchannelsUnrefed = true; + + /* These objects are created with Object.create(null), so they do not + * have a prototype, which means that for (... in ...) loops over them + * do not need to be filtered */ + // eslint-disable-disable-next-line:forin + for (const channelTarget in this.pool) { + const subchannelObjArray = this.pool[channelTarget]; + + const refedSubchannels = subchannelObjArray.filter( + (value) => !value.subchannel.unrefIfOneRef() + ); + + if (refedSubchannels.length > 0) { + allSubchannelsUnrefed = false; + } + + /* For each subchannel in the pool, try to unref it if it has + * exactly one ref (which is the ref from the pool itself). If that + * does happen, remove the subchannel from the pool */ + this.pool[channelTarget] = refedSubchannels; + } + /* Currently we do not delete keys with empty values. If that results + * in significant memory usage we should change it. */ + + // Cancel the cleanup task if all subchannels have been unrefed. + if (allSubchannelsUnrefed && this.cleanupTimer !== null) { + clearInterval(this.cleanupTimer); + this.cleanupTimer = null; + } + } + + /** + * Ensures that the cleanup task is spawned. + */ + ensureCleanupTask(): void { + if (this.cleanupTimer === null) { + this.cleanupTimer = setInterval(() => { + this.unrefUnusedSubchannels(); + }, REF_CHECK_INTERVAL); + + // Unref because this timer should not keep the event loop running. + // Call unref only if it exists to address electron/electron#21162 + this.cleanupTimer.unref?.(); + } + } + + /** + * Get a subchannel if one already exists with exactly matching parameters. + * Otherwise, create and save a subchannel with those parameters. + * @param channelTarget + * @param subchannelTarget + * @param channelArguments + * @param channelCredentials + */ + getOrCreateSubchannel( + channelTargetUri: GrpcUri, + subchannelTarget: SubchannelAddress, + channelArguments: ChannelOptions, + channelCredentials: ChannelCredentials + ): Subchannel { + this.ensureCleanupTask(); + const channelTarget = uriToString(channelTargetUri); + if (channelTarget in this.pool) { + const subchannelObjArray = this.pool[channelTarget]; + for (const subchannelObj of subchannelObjArray) { + if ( + subchannelAddressEqual( + subchannelTarget, + subchannelObj.subchannelAddress + ) && + channelOptionsEqual( + channelArguments, + subchannelObj.channelArguments + ) && + channelCredentials._equals(subchannelObj.channelCredentials) + ) { + return subchannelObj.subchannel; + } + } + } + // If we get here, no matching subchannel was found + const subchannel = new Subchannel( + channelTargetUri, + subchannelTarget, + channelArguments, + channelCredentials, + new Http2SubchannelConnector(channelTargetUri) + ); + if (!(channelTarget in this.pool)) { + this.pool[channelTarget] = []; + } + this.pool[channelTarget].push({ + subchannelAddress: subchannelTarget, + channelArguments, + channelCredentials, + subchannel, + }); + subchannel.ref(); + return subchannel; + } +} + +const globalSubchannelPool = new SubchannelPool(); + +/** + * Get either the global subchannel pool, or a new subchannel pool. + * @param global + */ +export function getSubchannelPool(global: boolean): SubchannelPool { + if (global) { + return globalSubchannelPool; + } else { + return new SubchannelPool(); + } +} diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts new file mode 100644 index 000000000..307f6b81e --- /dev/null +++ b/packages/grpc-js/src/subchannel.ts @@ -0,0 +1,415 @@ +/* + * Copyright 2019 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. + * + */ + +import { ChannelCredentials } from './channel-credentials'; +import { Metadata } from './metadata'; +import { ChannelOptions } from './channel-options'; +import { ConnectivityState } from './connectivity-state'; +import { BackoffTimeout, BackoffOptions } from './backoff-timeout'; +import * as logging from './logging'; +import { LogVerbosity, Status } from './constants'; +import { GrpcUri, uriToString } from './uri-parser'; +import { + SubchannelAddress, + subchannelAddressToString, +} from './subchannel-address'; +import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, unregisterChannelzRef } from './channelz'; +import { ConnectivityStateListener } from './subchannel-interface'; +import { SubchannelCallInterceptingListener } from './subchannel-call'; +import { SubchannelCall } from './subchannel-call'; +import { CallEventTracker, SubchannelConnector, Transport } from './transport'; + +const TRACER_NAME = 'subchannel'; + +/* setInterval and setTimeout only accept signed 32 bit integers. JS doesn't + * have a constant for the max signed 32 bit integer, so this is a simple way + * to calculate it */ +const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); + +export class Subchannel { + /** + * The subchannel's current connectivity state. Invariant: `session` === `null` + * if and only if `connectivityState` is IDLE or TRANSIENT_FAILURE. + */ + private connectivityState: ConnectivityState = ConnectivityState.IDLE; + /** + * The underlying http2 session used to make requests. + */ + private transport: Transport | null = null; + /** + * Indicates that the subchannel should transition from TRANSIENT_FAILURE to + * CONNECTING instead of IDLE when the backoff timeout ends. + */ + private continueConnecting = false; + /** + * A list of listener functions that will be called whenever the connectivity + * state changes. Will be modified by `addConnectivityStateListener` and + * `removeConnectivityStateListener` + */ + private stateListeners: Set = new Set(); + + private backoffTimeout: BackoffTimeout; + + private keepaliveTime: number; + /** + * Tracks channels and subchannel pools with references to this subchannel + */ + private refcount = 0; + + /** + * A string representation of the subchannel address, for logging/tracing + */ + private subchannelAddressString: string; + + // Channelz info + private readonly channelzEnabled: boolean = true; + private channelzRef: SubchannelRef; + private channelzTrace: ChannelzTrace; + private callTracker = new ChannelzCallTracker(); + private childrenTracker = new ChannelzChildrenTracker(); + + // Channelz socket info + private streamTracker = new ChannelzCallTracker(); + + /** + * A class representing a connection to a single backend. + * @param channelTarget The target string for the channel as a whole + * @param subchannelAddress The address for the backend that this subchannel + * will connect to + * @param options The channel options, plus any specific subchannel options + * for this subchannel + * @param credentials The channel credentials used to establish this + * connection + */ + constructor( + private channelTarget: GrpcUri, + private subchannelAddress: SubchannelAddress, + private options: ChannelOptions, + private credentials: ChannelCredentials, + private connector: SubchannelConnector + ) { + const backoffOptions: BackoffOptions = { + initialDelay: options['grpc.initial_reconnect_backoff_ms'], + maxDelay: options['grpc.max_reconnect_backoff_ms'], + }; + this.backoffTimeout = new BackoffTimeout(() => { + this.handleBackoffTimer(); + }, backoffOptions); + this.subchannelAddressString = subchannelAddressToString(subchannelAddress); + + this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; + + if (options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + this.channelzTrace = new ChannelzTrace(); + this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); + } + this.trace('Subchannel constructed with options ' + JSON.stringify(options, undefined, 2)); + } + + private getChannelzInfo(): SubchannelInfo { + return { + state: this.connectivityState, + trace: this.channelzTrace, + callTracker: this.callTracker, + children: this.childrenTracker.getChildLists(), + target: this.subchannelAddressString + }; + } + + private trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private refTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, 'subchannel_refcount', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private handleBackoffTimer() { + if (this.continueConnecting) { + this.transitionToState( + [ConnectivityState.TRANSIENT_FAILURE], + ConnectivityState.CONNECTING + ); + } else { + this.transitionToState( + [ConnectivityState.TRANSIENT_FAILURE], + ConnectivityState.IDLE + ); + } + } + + /** + * Start a backoff timer with the current nextBackoff timeout + */ + private startBackoff() { + this.backoffTimeout.runOnce(); + } + + private stopBackoff() { + this.backoffTimeout.stop(); + this.backoffTimeout.reset(); + } + + private startConnectingInternal() { + let options = this.options; + if (options['grpc.keepalive_time_ms']) { + const adjustedKeepaliveTime = Math.min(this.keepaliveTime, KEEPALIVE_MAX_TIME_MS); + options = {...options, 'grpc.keepalive_time_ms': adjustedKeepaliveTime}; + } + this.connector.connect(this.subchannelAddress, this.credentials, options).then( + transport => { + if (this.transitionToState([ConnectivityState.CONNECTING], ConnectivityState.READY)) { + this.transport = transport; + if (this.channelzEnabled) { + this.childrenTracker.refChild(transport.getChannelzRef()); + } + transport.addDisconnectListener((tooManyPings) => { + this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE); + if (tooManyPings && this.keepaliveTime > 0) { + this.keepaliveTime *= 2; + logging.log( + LogVerbosity.ERROR, + `Connection to ${uriToString(this.channelTarget)} at ${ + this.subchannelAddressString + } rejected by server because of excess pings. Increasing ping interval to ${ + this.keepaliveTime + } ms` + ); + } + }); + } + }, + error => { + this.transitionToState([ConnectivityState.CONNECTING], ConnectivityState.TRANSIENT_FAILURE); + } + ) + } + + /** + * Initiate a state transition from any element of oldStates to the new + * state. If the current connectivityState is not in oldStates, do nothing. + * @param oldStates The set of states to transition from + * @param newState The state to transition to + * @returns True if the state changed, false otherwise + */ + private transitionToState( + oldStates: ConnectivityState[], + newState: ConnectivityState + ): boolean { + if (oldStates.indexOf(this.connectivityState) === -1) { + return false; + } + this.trace( + ConnectivityState[this.connectivityState] + + ' -> ' + + ConnectivityState[newState] + ); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + } + const previousState = this.connectivityState; + this.connectivityState = newState; + switch (newState) { + case ConnectivityState.READY: + this.stopBackoff(); + break; + case ConnectivityState.CONNECTING: + this.startBackoff(); + this.startConnectingInternal(); + this.continueConnecting = false; + break; + case ConnectivityState.TRANSIENT_FAILURE: + if (this.channelzEnabled && this.transport) { + this.childrenTracker.unrefChild(this.transport.getChannelzRef()); + } + this.transport?.shutdown(); + this.transport = null; + /* If the backoff timer has already ended by the time we get to the + * TRANSIENT_FAILURE state, we want to immediately transition out of + * TRANSIENT_FAILURE as though the backoff timer is ending right now */ + if (!this.backoffTimeout.isRunning()) { + process.nextTick(() => { + this.handleBackoffTimer(); + }); + } + break; + case ConnectivityState.IDLE: + if (this.channelzEnabled && this.transport) { + this.childrenTracker.unrefChild(this.transport.getChannelzRef()); + } + this.transport?.shutdown(); + this.transport = null; + break; + default: + throw new Error(`Invalid state: unknown ConnectivityState ${newState}`); + } + for (const listener of this.stateListeners) { + listener(this, previousState, newState, this.keepaliveTime); + } + return true; + } + + ref() { + this.refTrace( + 'refcount ' + + this.refcount + + ' -> ' + + (this.refcount + 1) + ); + this.refcount += 1; + } + + unref() { + this.refTrace( + 'refcount ' + + this.refcount + + ' -> ' + + (this.refcount - 1) + ); + this.refcount -= 1; + if (this.refcount === 0) { + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); + } + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } + process.nextTick(() => { + this.transitionToState( + [ConnectivityState.CONNECTING, ConnectivityState.READY], + ConnectivityState.IDLE + ); + }); + } + } + + unrefIfOneRef(): boolean { + if (this.refcount === 1) { + this.unref(); + return true; + } + return false; + } + + createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener): SubchannelCall { + if (!this.transport) { + throw new Error('Cannot create call, subchannel not READY'); + } + let statsTracker: Partial; + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + this.streamTracker.addCallStarted(); + statsTracker = { + onCallEnd: status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + } + } + } else { + statsTracker = {}; + } + return this.transport.createCall(metadata, host, method, listener, statsTracker); + } + + /** + * If the subchannel is currently IDLE, start connecting and switch to the + * CONNECTING state. If the subchannel is current in TRANSIENT_FAILURE, + * the next time it would transition to IDLE, start connecting again instead. + * Otherwise, do nothing. + */ + startConnecting() { + process.nextTick(() => { + /* First, try to transition from IDLE to connecting. If that doesn't happen + * because the state is not currently IDLE, check if it is + * TRANSIENT_FAILURE, and if so indicate that it should go back to + * connecting after the backoff timer ends. Otherwise do nothing */ + if ( + !this.transitionToState( + [ConnectivityState.IDLE], + ConnectivityState.CONNECTING + ) + ) { + if (this.connectivityState === ConnectivityState.TRANSIENT_FAILURE) { + this.continueConnecting = true; + } + } + }); + } + + /** + * Get the subchannel's current connectivity state. + */ + getConnectivityState() { + return this.connectivityState; + } + + /** + * Add a listener function to be called whenever the subchannel's + * connectivity state changes. + * @param listener + */ + addConnectivityStateListener(listener: ConnectivityStateListener) { + this.stateListeners.add(listener); + } + + /** + * Remove a listener previously added with `addConnectivityStateListener` + * @param listener A reference to a function previously passed to + * `addConnectivityStateListener` + */ + removeConnectivityStateListener(listener: ConnectivityStateListener) { + this.stateListeners.delete(listener); + } + + /** + * Reset the backoff timeout, and immediately start connecting if in backoff. + */ + resetBackoff() { + process.nextTick(() => { + this.backoffTimeout.reset(); + this.transitionToState( + [ConnectivityState.TRANSIENT_FAILURE], + ConnectivityState.CONNECTING + ); + }); + } + + getAddress(): string { + return this.subchannelAddressString; + } + + getChannelzRef(): SubchannelRef { + return this.channelzRef; + } + + getRealSubchannel(): this { + return this; + } + + throttleKeepalive(newKeepaliveTime: number) { + if (newKeepaliveTime > this.keepaliveTime) { + this.keepaliveTime = newKeepaliveTime; + } + } +} diff --git a/packages/grpc-js/src/tls-helpers.ts b/packages/grpc-js/src/tls-helpers.ts new file mode 100644 index 000000000..3f7a62e7e --- /dev/null +++ b/packages/grpc-js/src/tls-helpers.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2019 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. + * + */ + +import * as fs from 'fs'; + +export const CIPHER_SUITES: string | undefined = + process.env.GRPC_SSL_CIPHER_SUITES; + +const DEFAULT_ROOTS_FILE_PATH = process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH; + +let defaultRootsData: Buffer | null = null; + +export function getDefaultRootsData(): Buffer | null { + if (DEFAULT_ROOTS_FILE_PATH) { + if (defaultRootsData === null) { + defaultRootsData = fs.readFileSync(DEFAULT_ROOTS_FILE_PATH); + } + return defaultRootsData; + } + return null; +} diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts new file mode 100644 index 000000000..8c0164e9d --- /dev/null +++ b/packages/grpc-js/src/transport.ts @@ -0,0 +1,706 @@ +/* + * Copyright 2023 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. + * + */ + +import * as http2 from 'http2'; +import { checkServerIdentity, CipherNameAndProtocol, ConnectionOptions, PeerCertificate, TLSSocket } from 'tls'; +import { StatusObject } from './call-interface'; +import { ChannelCredentials } from './channel-credentials'; +import { ChannelOptions } from './channel-options'; +import { ChannelzCallTracker, registerChannelzSocket, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; +import { LogVerbosity } from './constants'; +import { getProxiedConnection, ProxyConnectionResult } from './http_proxy'; +import * as logging from './logging'; +import { getDefaultAuthority } from './resolver'; +import { stringToSubchannelAddress, SubchannelAddress, subchannelAddressToString } from './subchannel-address'; +import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; +import * as net from 'net'; +import { Http2SubchannelCall, SubchannelCall, SubchannelCallInterceptingListener } from './subchannel-call'; +import { Metadata } from './metadata'; +import { getNextCallNumber } from './call-number'; + +const TRACER_NAME = 'transport'; +const FLOW_CONTROL_TRACER_NAME = 'transport_flowctrl'; + +const clientVersion = require('../../package.json').version; + +const { + HTTP2_HEADER_AUTHORITY, + HTTP2_HEADER_CONTENT_TYPE, + HTTP2_HEADER_METHOD, + HTTP2_HEADER_PATH, + HTTP2_HEADER_TE, + HTTP2_HEADER_USER_AGENT, +} = http2.constants; + +const KEEPALIVE_TIMEOUT_MS = 20000; + +export interface CallEventTracker { + addMessageSent(): void; + addMessageReceived(): void; + onCallEnd(status: StatusObject): void; + onStreamEnd(success: boolean): void; +} + +export interface TransportDisconnectListener { + (tooManyPings: boolean): void; +} + +export interface Transport { + getChannelzRef(): SocketRef; + getPeerName(): string; + getOptions(): ChannelOptions; + createCall( + metadata: Metadata, + host: string, + method: string, + listener: SubchannelCallInterceptingListener, + subchannelCallStatsTracker: Partial + ): SubchannelCall; + addDisconnectListener(listener: TransportDisconnectListener): void; + shutdown(): void; +} + +const tooManyPingsData: Buffer = Buffer.from('too_many_pings', 'ascii'); + +class Http2Transport implements Transport { + /** + * The amount of time in between sending pings + */ + private keepaliveTimeMs: number = -1; + /** + * The amount of time to wait for an acknowledgement after sending a ping + */ + private keepaliveTimeoutMs: number = KEEPALIVE_TIMEOUT_MS; + /** + * Timer reference for timeout that indicates when to send the next ping + */ + private keepaliveTimerId: NodeJS.Timeout | null = null; + /** + * Indicates that the keepalive timer ran out while there were no active + * calls, and a ping should be sent the next time a call starts. + */ + private pendingSendKeepalivePing = false; + /** + * Timer reference tracking when the most recent ping will be considered lost + */ + private keepaliveTimeoutId: NodeJS.Timeout | null = null; + /** + * Indicates whether keepalive pings should be sent without any active calls + */ + private keepaliveWithoutCalls = false; + + private userAgent: string; + + private activeCalls: Set = new Set(); + + private subchannelAddressString: string; + + private disconnectListeners: TransportDisconnectListener[] = []; + + private disconnectHandled = false; + + // Channelz info + private channelzRef: SocketRef; + private readonly channelzEnabled: boolean = true; + private streamTracker = new ChannelzCallTracker(); + private keepalivesSent = 0; + private messagesSent = 0; + private messagesReceived = 0; + private lastMessageSentTimestamp: Date | null = null; + private lastMessageReceivedTimestamp: Date | null = null; + + constructor( + private session: http2.ClientHttp2Session, + subchannelAddress: SubchannelAddress, + private options: ChannelOptions, + /** + * Name of the remote server, if it is not the same as the subchannel + * address, i.e. if connecting through an HTTP CONNECT proxy. + */ + private remoteName: string | null + ) { + /* Populate subchannelAddressString and channelzRef before doing anything + * else, because they are used in the trace methods. */ + this.subchannelAddressString = subchannelAddressToString(subchannelAddress); + + if (options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + this.channelzRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); + // Build user-agent string. + this.userAgent = [ + options['grpc.primary_user_agent'], + `grpc-node-js/${clientVersion}`, + options['grpc.secondary_user_agent'], + ] + .filter((e) => e) + .join(' '); // remove falsey values first + + if ('grpc.keepalive_time_ms' in options) { + this.keepaliveTimeMs = options['grpc.keepalive_time_ms']!; + } + if ('grpc.keepalive_timeout_ms' in options) { + this.keepaliveTimeoutMs = options['grpc.keepalive_timeout_ms']!; + } + if ('grpc.keepalive_permit_without_calls' in options) { + this.keepaliveWithoutCalls = + options['grpc.keepalive_permit_without_calls'] === 1; + } else { + this.keepaliveWithoutCalls = false; + } + + session.once('close', () => { + this.trace('session closed'); + this.stopKeepalivePings(); + this.handleDisconnect(); + }); + session.once('goaway', (errorCode: number, lastStreamID: number, opaqueData: Buffer) => { + let tooManyPings = false; + /* See the last paragraph of + * https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md#basic-keepalive */ + if ( + errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM && + opaqueData.equals(tooManyPingsData) + ) { + tooManyPings = true; + } + this.trace( + 'connection closed by GOAWAY with code ' + + errorCode + ); + this.reportDisconnectToOwner(tooManyPings); + }); + session.once('error', error => { + /* Do nothing here. Any error should also trigger a close event, which is + * where we want to handle that. */ + this.trace( + 'connection closed with error ' + + (error as Error).message + ); + }); + if (logging.isTracerEnabled(TRACER_NAME)) { + session.on('remoteSettings', (settings: http2.Settings) => { + this.trace( + 'new settings received' + + (this.session !== session ? ' on the old connection' : '') + + ': ' + + JSON.stringify(settings) + ); + }); + session.on('localSettings', (settings: http2.Settings) => { + this.trace( + 'local settings acknowledged by remote' + + (this.session !== session ? ' on the old connection' : '') + + ': ' + + JSON.stringify(settings) + ); + }); + } + /* Start the keepalive timer last, because this can trigger trace logs, + * which should only happen after everything else is set up. */ + if (this.keepaliveWithoutCalls) { + this.maybeStartKeepalivePingTimer(); + } + } + + private getChannelzInfo(): SocketInfo { + const sessionSocket = this.session.socket; + const remoteAddress = sessionSocket.remoteAddress ? stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; + const localAddress = sessionSocket.localAddress ? stringToSubchannelAddress(sessionSocket.localAddress, sessionSocket.localPort) : null; + let tlsInfo: TlsInfo | null; + if (this.session.encrypted) { + const tlsSocket: TLSSocket = sessionSocket as TLSSocket; + const cipherInfo: CipherNameAndProtocol & {standardName?: string} = tlsSocket.getCipher(); + const certificate = tlsSocket.getCertificate(); + const peerCertificate = tlsSocket.getPeerCertificate(); + tlsInfo = { + cipherSuiteStandardName: cipherInfo.standardName ?? null, + cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, + localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, + remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null + }; + } else { + tlsInfo = null; + } + const socketInfo: SocketInfo = { + remoteAddress: remoteAddress, + localAddress: localAddress, + security: tlsInfo, + remoteName: this.remoteName, + streamsStarted: this.streamTracker.callsStarted, + streamsSucceeded: this.streamTracker.callsSucceeded, + streamsFailed: this.streamTracker.callsFailed, + messagesSent: this.messagesSent, + messagesReceived: this.messagesReceived, + keepAlivesSent: this.keepalivesSent, + lastLocalStreamCreatedTimestamp: this.streamTracker.lastCallStartedTimestamp, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: this.lastMessageSentTimestamp, + lastMessageReceivedTimestamp: this.lastMessageReceivedTimestamp, + localFlowControlWindow: this.session.state.localWindowSize ?? null, + remoteFlowControlWindow: this.session.state.remoteWindowSize ?? null + }; + return socketInfo; + } + + private trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private keepaliveTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, 'keepalive', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private flowControlTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, FLOW_CONTROL_TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private internalsTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, 'transport_internals', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + /** + * Indicate to the owner of this object that this transport should no longer + * be used. That happens if the connection drops, or if the server sends a + * GOAWAY. + * @param tooManyPings If true, this was triggered by a GOAWAY with data + * indicating that the session was closed becaues the client sent too many + * pings. + * @returns + */ + private reportDisconnectToOwner(tooManyPings: boolean) { + if (this.disconnectHandled) { + return; + } + this.disconnectHandled = true; + this.disconnectListeners.forEach(listener => listener(tooManyPings)); + } + + /** + * Handle connection drops, but not GOAWAYs. + */ + private handleDisconnect() { + this.reportDisconnectToOwner(false); + /* Give calls an event loop cycle to finish naturally before reporting the + * disconnnection to them. */ + setImmediate(() => { + for (const call of this.activeCalls) { + call.onDisconnect(); + } + }); + } + + addDisconnectListener(listener: TransportDisconnectListener): void { + this.disconnectListeners.push(listener); + } + + private clearKeepaliveTimer() { + if (!this.keepaliveTimerId) { + return; + } + clearTimeout(this.keepaliveTimerId); + this.keepaliveTimerId = null; + } + + private clearKeepaliveTimeout() { + if (!this.keepaliveTimeoutId) { + return; + } + clearTimeout(this.keepaliveTimeoutId); + this.keepaliveTimeoutId = null; + } + + private canSendPing() { + return this.keepaliveTimeMs > 0 && (this.keepaliveWithoutCalls || this.activeCalls.size > 0); + } + + private maybeSendPing() { + this.clearKeepaliveTimer(); + if (!this.canSendPing()) { + this.pendingSendKeepalivePing = true; + return; + } + if (this.channelzEnabled) { + this.keepalivesSent += 1; + } + this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms'); + if (!this.keepaliveTimeoutId) { + this.keepaliveTimeoutId = setTimeout(() => { + this.keepaliveTrace('Ping timeout passed without response'); + this.handleDisconnect(); + }, this.keepaliveTimeoutMs); + this.keepaliveTimeoutId.unref?.(); + } + try { + this.session!.ping( + (err: Error | null, duration: number, payload: Buffer) => { + this.keepaliveTrace('Received ping response'); + this.clearKeepaliveTimeout(); + this.maybeStartKeepalivePingTimer(); + } + ); + } catch (e) { + /* If we fail to send a ping, the connection is no longer functional, so + * we should discard it. */ + this.handleDisconnect(); + } + } + + /** + * Starts the keepalive ping timer if appropriate. If the timer already ran + * out while there were no active requests, instead send a ping immediately. + * If the ping timer is already running or a ping is currently in flight, + * instead do nothing and wait for them to resolve. + */ + private maybeStartKeepalivePingTimer() { + if (!this.canSendPing()) { + return; + } + if (this.pendingSendKeepalivePing) { + this.pendingSendKeepalivePing = false; + this.maybeSendPing(); + } else if (!this.keepaliveTimerId && !this.keepaliveTimeoutId) { + this.keepaliveTrace('Starting keepalive timer for ' + this.keepaliveTimeMs + 'ms'); + this.keepaliveTimerId = setTimeout(() => { + this.maybeSendPing(); + }, this.keepaliveTimeMs).unref?.(); + } + /* Otherwise, there is already either a keepalive timer or a ping pending, + * wait for those to resolve. */ + } + + private stopKeepalivePings() { + if (this.keepaliveTimerId) { + clearTimeout(this.keepaliveTimerId); + this.keepaliveTimerId = null; + } + this.clearKeepaliveTimeout(); + } + + private removeActiveCall(call: Http2SubchannelCall) { + this.activeCalls.delete(call); + if (this.activeCalls.size === 0) { + this.session.unref(); + } + } + + private addActiveCall(call: Http2SubchannelCall) { + this.activeCalls.add(call); + if (this.activeCalls.size === 1) { + this.session.ref(); + if (!this.keepaliveWithoutCalls) { + this.maybeStartKeepalivePingTimer(); + } + } + } + + createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener, subchannelCallStatsTracker: Partial): Http2SubchannelCall { + const headers = metadata.toHttp2Headers(); + headers[HTTP2_HEADER_AUTHORITY] = host; + headers[HTTP2_HEADER_USER_AGENT] = this.userAgent; + headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; + headers[HTTP2_HEADER_METHOD] = 'POST'; + headers[HTTP2_HEADER_PATH] = method; + headers[HTTP2_HEADER_TE] = 'trailers'; + let http2Stream: http2.ClientHttp2Stream; + /* In theory, if an error is thrown by session.request because session has + * become unusable (e.g. because it has received a goaway), this subchannel + * should soon see the corresponding close or goaway event anyway and leave + * READY. But we have seen reports that this does not happen + * (https://github.com/googleapis/nodejs-firestore/issues/1023#issuecomment-653204096) + * so for defense in depth, we just discard the session when we see an + * error here. + */ + try { + http2Stream = this.session!.request(headers); + } catch (e) { + this.handleDisconnect(); + throw e; + } + this.flowControlTrace( + 'local window size: ' + + this.session.state.localWindowSize + + ' remote window size: ' + + this.session.state.remoteWindowSize + ); + this.internalsTrace( + 'session.closed=' + + this.session.closed + + ' session.destroyed=' + + this.session.destroyed + + ' session.socket.destroyed=' + + this.session.socket.destroyed); + let eventTracker: CallEventTracker; + let call: Http2SubchannelCall; + if (this.channelzEnabled) { + this.streamTracker.addCallStarted(); + eventTracker = { + addMessageSent: () => { + this.messagesSent += 1; + this.lastMessageSentTimestamp = new Date(); + subchannelCallStatsTracker.addMessageSent?.(); + }, + addMessageReceived: () => { + this.messagesReceived += 1; + this.lastMessageReceivedTimestamp = new Date(); + subchannelCallStatsTracker.addMessageReceived?.(); + }, + onCallEnd: status => { + subchannelCallStatsTracker.onCallEnd?.(status); + this.removeActiveCall(call); + }, + onStreamEnd: success => { + if (success) { + this.streamTracker.addCallSucceeded(); + } else { + this.streamTracker.addCallFailed(); + } + subchannelCallStatsTracker.onStreamEnd?.(success); + } + } + } else { + eventTracker = { + addMessageSent: () => { + subchannelCallStatsTracker.addMessageSent?.(); + }, + addMessageReceived: () => { + subchannelCallStatsTracker.addMessageReceived?.(); + }, + onCallEnd: (status) => { + subchannelCallStatsTracker.onCallEnd?.(status); + this.removeActiveCall(call); + }, + onStreamEnd: (success) => { + subchannelCallStatsTracker.onStreamEnd?.(success); + } + } + } + call = new Http2SubchannelCall(http2Stream, eventTracker, listener, this, getNextCallNumber()); + this.addActiveCall(call); + return call; + } + + getChannelzRef(): SocketRef { + return this.channelzRef; + } + + getPeerName() { + return this.subchannelAddressString; + } + + getOptions() { + return this.options; + } + + shutdown() { + this.session.close(); + unregisterChannelzRef(this.channelzRef); + } +} + +export interface SubchannelConnector { + connect(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions): Promise; + shutdown(): void; +} + +export class Http2SubchannelConnector implements SubchannelConnector { + private session: http2.ClientHttp2Session | null = null; + private isShutdown = false; + constructor(private channelTarget: GrpcUri) {} + private trace(text: string) { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, this.channelTarget + ' ' + text); + } + private createSession(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions, proxyConnectionResult: ProxyConnectionResult): Promise { + if (this.isShutdown) { + return Promise.reject(); + } + return new Promise((resolve, reject) => { + let remoteName: string | null; + if (proxyConnectionResult.realTarget) { + remoteName = uriToString(proxyConnectionResult.realTarget); + this.trace('creating HTTP/2 session through proxy to ' + uriToString(proxyConnectionResult.realTarget)); + } else { + remoteName = null; + this.trace('creating HTTP/2 session to ' + subchannelAddressToString(address)); + } + const targetAuthority = getDefaultAuthority( + proxyConnectionResult.realTarget ?? this.channelTarget + ); + let connectionOptions: http2.SecureClientSessionOptions = + credentials._getConnectionOptions() || {}; + connectionOptions.maxSendHeaderBlockLength = Number.MAX_SAFE_INTEGER; + if ('grpc-node.max_session_memory' in options) { + connectionOptions.maxSessionMemory = options[ + 'grpc-node.max_session_memory' + ]; + } else { + /* By default, set a very large max session memory limit, to effectively + * disable enforcement of the limit. Some testing indicates that Node's + * behavior degrades badly when this limit is reached, so we solve that + * by disabling the check entirely. */ + connectionOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; + } + let addressScheme = 'http://'; + if ('secureContext' in connectionOptions) { + addressScheme = 'https://'; + // If provided, the value of grpc.ssl_target_name_override should be used + // to override the target hostname when checking server identity. + // This option is used for testing only. + if (options['grpc.ssl_target_name_override']) { + const sslTargetNameOverride = options[ + 'grpc.ssl_target_name_override' + ]!; + connectionOptions.checkServerIdentity = ( + host: string, + cert: PeerCertificate + ): Error | undefined => { + return checkServerIdentity(sslTargetNameOverride, cert); + }; + connectionOptions.servername = sslTargetNameOverride; + } else { + const authorityHostname = + splitHostPort(targetAuthority)?.host ?? 'localhost'; + // We want to always set servername to support SNI + connectionOptions.servername = authorityHostname; + } + if (proxyConnectionResult.socket) { + /* This is part of the workaround for + * https://github.com/nodejs/node/issues/32922. Without that bug, + * proxyConnectionResult.socket would always be a plaintext socket and + * this would say + * connectionOptions.socket = proxyConnectionResult.socket; */ + connectionOptions.createConnection = (authority, option) => { + return proxyConnectionResult.socket!; + }; + } + } else { + /* In all but the most recent versions of Node, http2.connect does not use + * the options when establishing plaintext connections, so we need to + * establish that connection explicitly. */ + connectionOptions.createConnection = (authority, option) => { + if (proxyConnectionResult.socket) { + return proxyConnectionResult.socket; + } else { + /* net.NetConnectOpts is declared in a way that is more restrictive + * than what net.connect will actually accept, so we use the type + * assertion to work around that. */ + return net.connect(address); + } + }; + } + + connectionOptions = { + ...connectionOptions, + ...address, + }; + + /* http2.connect uses the options here: + * https://github.com/nodejs/node/blob/70c32a6d190e2b5d7b9ff9d5b6a459d14e8b7d59/lib/internal/http2/core.js#L3028-L3036 + * The spread operator overides earlier values with later ones, so any port + * or host values in the options will be used rather than any values extracted + * from the first argument. In addition, the path overrides the host and port, + * as documented for plaintext connections here: + * https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener + * and for TLS connections here: + * https://nodejs.org/api/tls.html#tls_tls_connect_options_callback. In + * earlier versions of Node, http2.connect passes these options to + * tls.connect but not net.connect, so in the insecure case we still need + * to set the createConnection option above to create the connection + * explicitly. We cannot do that in the TLS case because http2.connect + * passes necessary additional options to tls.connect. + * The first argument just needs to be parseable as a URL and the scheme + * determines whether the connection will be established over TLS or not. + */ + const session = http2.connect( + addressScheme + targetAuthority, + connectionOptions + ); + this.session = session; + session.unref(); + session.once('connect', () => { + session.removeAllListeners(); + resolve(new Http2Transport(session, address, options, remoteName)); + this.session = null; + }); + session.once('close', () => { + this.session = null; + reject(); + }); + session.once('error', error => { + this.trace('connection failed with error ' + (error as Error).message) + }); + }); + } + connect(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions): Promise { + if (this.isShutdown) { + return Promise.reject(); + } + /* Pass connection options through to the proxy so that it's able to + * upgrade it's connection to support tls if needed. + * This is a workaround for https://github.com/nodejs/node/issues/32922 + * See https://github.com/grpc/grpc-node/pull/1369 for more info. */ + const connectionOptions: ConnectionOptions = + credentials._getConnectionOptions() || {}; + + if ('secureContext' in connectionOptions) { + connectionOptions.ALPNProtocols = ['h2']; + // If provided, the value of grpc.ssl_target_name_override should be used + // to override the target hostname when checking server identity. + // This option is used for testing only. + if (options['grpc.ssl_target_name_override']) { + const sslTargetNameOverride = options[ + 'grpc.ssl_target_name_override' + ]!; + connectionOptions.checkServerIdentity = ( + host: string, + cert: PeerCertificate + ): Error | undefined => { + return checkServerIdentity(sslTargetNameOverride, cert); + }; + connectionOptions.servername = sslTargetNameOverride; + } else { + if ('grpc.http_connect_target' in options) { + /* This is more or less how servername will be set in createSession + * if a connection is successfully established through the proxy. + * If the proxy is not used, these connectionOptions are discarded + * anyway */ + const targetPath = getDefaultAuthority( + parseUri(options['grpc.http_connect_target'] as string) ?? { + path: 'localhost', + } + ); + const hostPort = splitHostPort(targetPath); + connectionOptions.servername = hostPort?.host ?? targetPath; + } + } + } + + return getProxiedConnection( + address, + options, + connectionOptions + ).then( + result => this.createSession(address, credentials, options, result) + ); + } + + shutdown(): void { + this.isShutdown = true; + this.session?.close(); + this.session = null; + } +} diff --git a/packages/grpc-js/src/uri-parser.ts b/packages/grpc-js/src/uri-parser.ts new file mode 100644 index 000000000..20c3d53b3 --- /dev/null +++ b/packages/grpc-js/src/uri-parser.ts @@ -0,0 +1,114 @@ +/* + * Copyright 2020 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. + * + */ + +export interface GrpcUri { + scheme?: string; + authority?: string; + path: string; +} + +/* + * The groups correspond to URI parts as follows: + * 1. scheme + * 2. authority + * 3. path + */ +const URI_REGEX = /^(?:([A-Za-z0-9+.-]+):)?(?:\/\/([^/]*)\/)?(.+)$/; + +export function parseUri(uriString: string): GrpcUri | null { + const parsedUri = URI_REGEX.exec(uriString); + if (parsedUri === null) { + return null; + } + return { + scheme: parsedUri[1], + authority: parsedUri[2], + path: parsedUri[3], + }; +} + +export interface HostPort { + host: string; + port?: number; +} + +const NUMBER_REGEX = /^\d+$/; + +export function splitHostPort(path: string): HostPort | null { + if (path.startsWith('[')) { + const hostEnd = path.indexOf(']'); + if (hostEnd === -1) { + return null; + } + const host = path.substring(1, hostEnd); + /* Only an IPv6 address should be in bracketed notation, and an IPv6 + * address should have at least one colon */ + if (host.indexOf(':') === -1) { + return null; + } + if (path.length > hostEnd + 1) { + if (path[hostEnd + 1] === ':') { + const portString = path.substring(hostEnd + 2); + if (NUMBER_REGEX.test(portString)) { + return { + host: host, + port: +portString, + }; + } else { + return null; + } + } else { + return null; + } + } else { + return { + host, + }; + } + } else { + const splitPath = path.split(':'); + /* Exactly one colon means that this is host:port. Zero colons means that + * there is no port. And multiple colons means that this is a bare IPv6 + * address with no port */ + if (splitPath.length === 2) { + if (NUMBER_REGEX.test(splitPath[1])) { + return { + host: splitPath[0], + port: +splitPath[1], + }; + } else { + return null; + } + } else { + return { + host: path, + }; + } + } +} + +export function uriToString(uri: GrpcUri): string { + let result = ''; + if (uri.scheme !== undefined) { + result += uri.scheme + ':'; + } + if (uri.authority !== undefined) { + result += '//' + uri.authority + '/'; + } + result += uri.path; + return result; +} diff --git a/packages/grpc-js-core/test/common.ts b/packages/grpc-js/test/common.ts similarity index 59% rename from packages/grpc-js-core/test/common.ts rename to packages/grpc-js/test/common.ts index 487ced1f2..24cb71650 100644 --- a/packages/grpc-js-core/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -1,5 +1,33 @@ +/* + * Copyright 2019 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. + * + */ + +import * as loader from '@grpc/proto-loader'; import * as assert from 'assert'; +import { GrpcObject, loadPackageDefinition } from '../src/make-client'; + +const protoLoaderOptions = { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, +}; + export function mockFunction(): never { throw new Error('Not implemented'); } @@ -17,8 +45,10 @@ export namespace assert2 { try { return fn(); } catch (e) { - assert.throws(() => {throw e}); - throw e; // for type safety only + assert.throws(() => { + throw e; + }); + throw e; // for type safety only } } @@ -28,7 +58,7 @@ export namespace assert2 { */ function mustCallsSatisfied(): boolean { let result = true; - toCall.forEach((value) => { + toCall.forEach(value => { result = result && value === 0; }); return result; @@ -42,7 +72,10 @@ export namespace assert2 { * Wraps a function to keep track of whether it was called or not. * @param fn The function to wrap. */ - export function mustCall(fn: (...args: any[]) => T): (...args: any[]) => T { + // tslint:disable:no-any + export function mustCall( + fn: (...args: any[]) => T + ): (...args: any[]) => T { const existingValue = toCall.get(fn); if (existingValue !== undefined) { toCall.set(fn, existingValue + 1); @@ -77,3 +110,8 @@ export namespace assert2 { } } } + +export function loadProtoFile(file: string): GrpcObject { + const packageDefinition = loader.loadSync(file, protoLoaderOptions); + return loadPackageDefinition(packageDefinition); +} diff --git a/packages/grpc-js-core/test/fixtures/README b/packages/grpc-js/test/fixtures/README similarity index 100% rename from packages/grpc-js-core/test/fixtures/README rename to packages/grpc-js/test/fixtures/README diff --git a/packages/grpc-js-core/test/fixtures/ca.pem b/packages/grpc-js/test/fixtures/ca.pem similarity index 100% rename from packages/grpc-js-core/test/fixtures/ca.pem rename to packages/grpc-js/test/fixtures/ca.pem diff --git a/packages/grpc-js/test/fixtures/echo_service.proto b/packages/grpc-js/test/fixtures/echo_service.proto new file mode 100644 index 000000000..20b3bfe02 --- /dev/null +++ b/packages/grpc-js/test/fixtures/echo_service.proto @@ -0,0 +1,33 @@ +/* + * Copyright 2019 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"; + +message EchoMessage { + string value = 1; + int32 value2 = 2; +} + +service EchoService { + rpc Echo (EchoMessage) returns (EchoMessage); + + rpc EchoClientStream (stream EchoMessage) returns (EchoMessage); + + rpc EchoServerStream (EchoMessage) returns (stream EchoMessage); + + rpc EchoBidiStream (stream EchoMessage) returns (stream EchoMessage); +} diff --git a/packages/grpc-js/test/fixtures/math.proto b/packages/grpc-js/test/fixtures/math.proto new file mode 100644 index 000000000..ca59a7afe --- /dev/null +++ b/packages/grpc-js/test/fixtures/math.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +package math; + +message DivArgs { + int64 dividend = 1; + int64 divisor = 2; +} + +message DivReply { + int64 quotient = 1; + int64 remainder = 2; +} + +message FibArgs { + int64 limit = 1; +} + +message Num { + int64 num = 1; +} + +message FibReply { + int64 count = 1; +} + +service Math { + // Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient + // and remainder. + rpc Div (DivArgs) returns (DivReply) { + } + + // DivMany accepts an arbitrary number of division args from the client stream + // and sends back the results in the reply stream. The stream continues until + // the client closes its end; the server does the same after sending all the + // replies. The stream ends immediately if either end aborts. + rpc DivMany (stream DivArgs) returns (stream DivReply) { + } + + // Fib generates numbers in the Fibonacci sequence. If FibArgs.limit > 0, Fib + // generates up to limit numbers; otherwise it continues until the call is + // canceled. Unlike Fib above, Fib has no final FibReply. + rpc Fib (FibArgs) returns (stream Num) { + } + + // Sum sums a stream of numbers, returning the final result once the stream + // is closed. + rpc Sum (stream Num) returns (Num) { + } +} diff --git a/packages/grpc-js-core/test/fixtures/server1.key b/packages/grpc-js/test/fixtures/server1.key similarity index 100% rename from packages/grpc-js-core/test/fixtures/server1.key rename to packages/grpc-js/test/fixtures/server1.key diff --git a/packages/grpc-js-core/test/fixtures/server1.pem b/packages/grpc-js/test/fixtures/server1.pem similarity index 100% rename from packages/grpc-js-core/test/fixtures/server1.pem rename to packages/grpc-js/test/fixtures/server1.pem diff --git a/packages/grpc-js/test/fixtures/test_service.proto b/packages/grpc-js/test/fixtures/test_service.proto new file mode 100644 index 000000000..2a7a303f3 --- /dev/null +++ b/packages/grpc-js/test/fixtures/test_service.proto @@ -0,0 +1,44 @@ +/* + * Copyright 2019 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"; + +message Request { + bool error = 1; + string message = 2; + int32 errorAfter = 3; + int32 responseLength = 4; +} + +message Response { + int32 count = 1; + string message = 2; +} + +service TestService { + rpc Unary (Request) returns (Response) { + } + + rpc ClientStream (stream Request) returns (Response) { + } + + rpc ServerStream (Request) returns (stream Response) { + } + + rpc BidiStream (stream Request) returns (stream Response) { + } +} diff --git a/packages/grpc-js/test/generated/Request.ts b/packages/grpc-js/test/generated/Request.ts new file mode 100644 index 000000000..d64ebb6ea --- /dev/null +++ b/packages/grpc-js/test/generated/Request.ts @@ -0,0 +1,14 @@ +// Original file: test/fixtures/test_service.proto + + +export interface Request { + 'error'?: (boolean); + 'message'?: (string); + 'errorAfter'?: (number); +} + +export interface Request__Output { + 'error': (boolean); + 'message': (string); + 'errorAfter': (number); +} diff --git a/packages/grpc-js/test/generated/Response.ts b/packages/grpc-js/test/generated/Response.ts new file mode 100644 index 000000000..465ab7203 --- /dev/null +++ b/packages/grpc-js/test/generated/Response.ts @@ -0,0 +1,12 @@ +// Original file: test/fixtures/test_service.proto + + +export interface Response { + 'count'?: (number); + 'message'?: (string); +} + +export interface Response__Output { + 'count': (number); + 'message': (string); +} diff --git a/packages/grpc-js/test/generated/TestService.ts b/packages/grpc-js/test/generated/TestService.ts new file mode 100644 index 000000000..e477c99b5 --- /dev/null +++ b/packages/grpc-js/test/generated/TestService.ts @@ -0,0 +1,55 @@ +// Original file: test/fixtures/test_service.proto + +import type * as grpc from './../../src/index' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { Request as _Request, Request__Output as _Request__Output } from './Request'; +import type { Response as _Response, Response__Output as _Response__Output } from './Response'; + +export interface TestServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; + + ClientStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + ClientStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + ClientStream(options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + ClientStream(callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + clientStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + clientStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + clientStream(options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + clientStream(callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + + ServerStream(argument: _Request, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; + ServerStream(argument: _Request, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; + serverStream(argument: _Request, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; + serverStream(argument: _Request, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; + + Unary(argument: _Request, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + Unary(argument: _Request, metadata: grpc.Metadata, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + Unary(argument: _Request, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + Unary(argument: _Request, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + unary(argument: _Request, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + unary(argument: _Request, metadata: grpc.Metadata, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + unary(argument: _Request, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + unary(argument: _Request, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + +} + +export interface TestServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_Request__Output, _Response>; + + ClientStream: grpc.handleClientStreamingCall<_Request__Output, _Response>; + + ServerStream: grpc.handleServerStreamingCall<_Request__Output, _Response>; + + Unary: grpc.handleUnaryCall<_Request__Output, _Response>; + +} + +export interface TestServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_Request, _Response, _Request__Output, _Response__Output> + ClientStream: MethodDefinition<_Request, _Response, _Request__Output, _Response__Output> + ServerStream: MethodDefinition<_Request, _Response, _Request__Output, _Response__Output> + Unary: MethodDefinition<_Request, _Response, _Request__Output, _Response__Output> +} diff --git a/packages/grpc-js/test/generated/test_service.ts b/packages/grpc-js/test/generated/test_service.ts new file mode 100644 index 000000000..364acddeb --- /dev/null +++ b/packages/grpc-js/test/generated/test_service.ts @@ -0,0 +1,15 @@ +import type * as grpc from '../../src/index'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { TestServiceClient as _TestServiceClient, TestServiceDefinition as _TestServiceDefinition } from './TestService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + Request: MessageTypeDefinition + Response: MessageTypeDefinition + TestService: SubtypeConstructor & { service: _TestServiceDefinition } +} + diff --git a/packages/grpc-js/test/test-call-credentials.ts b/packages/grpc-js/test/test-call-credentials.ts new file mode 100644 index 000000000..e952c5a10 --- /dev/null +++ b/packages/grpc-js/test/test-call-credentials.ts @@ -0,0 +1,161 @@ +/* + * Copyright 2019 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. + * + */ + +import * as assert from 'assert'; + +import { + CallCredentials, + CallMetadataGenerator, +} from '../src/call-credentials'; +import { Metadata } from '../src/metadata'; + +// Metadata generators + +function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator { + return (options, cb) => { + const metadata = new Metadata(); + metadata.add('msElapsed', `${ms}`); + setTimeout(() => cb(null, metadata), ms); + }; +} + +const generateFromServiceURL: CallMetadataGenerator = (options, cb) => { + const metadata: Metadata = new Metadata(); + metadata.add('service_url', options.service_url); + cb(null, metadata); +}; +const generateWithError: CallMetadataGenerator = (options, cb) => + cb(new Error()); + +// Tests + +describe('CallCredentials', () => { + describe('createFromMetadataGenerator', () => { + it('should accept a metadata generator', () => { + assert.doesNotThrow(() => + CallCredentials.createFromMetadataGenerator(generateFromServiceURL) + ); + }); + }); + + describe('compose', () => { + it('should accept a CallCredentials object and return a new object', () => { + const callCredentials1 = CallCredentials.createFromMetadataGenerator( + generateFromServiceURL + ); + const callCredentials2 = CallCredentials.createFromMetadataGenerator( + generateFromServiceURL + ); + const combinedCredentials = callCredentials1.compose(callCredentials2); + assert.notStrictEqual(combinedCredentials, callCredentials1); + assert.notStrictEqual(combinedCredentials, callCredentials2); + }); + + it('should be chainable', () => { + const callCredentials1 = CallCredentials.createFromMetadataGenerator( + generateFromServiceURL + ); + const callCredentials2 = CallCredentials.createFromMetadataGenerator( + generateFromServiceURL + ); + assert.doesNotThrow(() => { + callCredentials1 + .compose(callCredentials2) + .compose(callCredentials2) + .compose(callCredentials2); + }); + }); + }); + + describe('generateMetadata', () => { + it('should call the function passed to createFromMetadataGenerator', async () => { + const callCredentials = CallCredentials.createFromMetadataGenerator( + generateFromServiceURL + ); + let metadata: Metadata; + try { + metadata = await callCredentials.generateMetadata({ + service_url: 'foo', + }); + } catch (err) { + throw err; + } + assert.deepStrictEqual(metadata.get('service_url'), ['foo']); + }); + + it('should emit an error if the associated metadataGenerator does', async () => { + const callCredentials = CallCredentials.createFromMetadataGenerator( + generateWithError + ); + let metadata: Metadata | null = null; + try { + metadata = await callCredentials.generateMetadata({ service_url: '' }); + } catch (err) { + assert.ok(err instanceof Error); + } + assert.strictEqual(metadata, null); + }); + + it('should combine metadata from multiple generators', async () => { + const [callCreds1, callCreds2, callCreds3, callCreds4] = [ + 50, + 100, + 150, + 200, + ].map(ms => { + const generator: CallMetadataGenerator = makeAfterMsElapsedGenerator( + ms + ); + return CallCredentials.createFromMetadataGenerator(generator); + }); + const testCases = [ + { + credentials: callCreds1 + .compose(callCreds2) + .compose(callCreds3) + .compose(callCreds4), + expected: ['50', '100', '150', '200'], + }, + { + credentials: callCreds4.compose( + callCreds3.compose(callCreds2.compose(callCreds1)) + ), + expected: ['200', '150', '100', '50'], + }, + { + credentials: callCreds3.compose( + callCreds4.compose(callCreds1).compose(callCreds2) + ), + expected: ['150', '200', '50', '100'], + }, + ]; + // Try each test case and make sure the msElapsed field is as expected + await Promise.all( + testCases.map(async testCase => { + const { credentials, expected } = testCase; + let metadata: Metadata; + try { + metadata = await credentials.generateMetadata({ service_url: '' }); + } catch (err) { + throw err; + } + assert.deepStrictEqual(metadata.get('msElapsed'), expected); + }) + ); + }); + }); +}); diff --git a/packages/grpc-js/test/test-call-propagation.ts b/packages/grpc-js/test/test-call-propagation.ts new file mode 100644 index 000000000..4a2619f1f --- /dev/null +++ b/packages/grpc-js/test/test-call-propagation.ts @@ -0,0 +1,251 @@ +/* + * Copyright 2020 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. + * + */ + +import * as assert from 'assert'; + +import * as grpc from '../src'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; + +import { loadProtoFile } from './common'; + +function multiDone(done: () => void, target: number) { + let count = 0; + return () => { + count++; + if (count >= target) { + done(); + } + } +} + +describe('Call propagation', () => { + let server: grpc.Server; + let Client: ServiceClientConstructor; + let client: ServiceClient; + let proxyServer: grpc.Server; + let proxyClient: ServiceClient; + + before((done) => { + Client = loadProtoFile(__dirname + '/fixtures/test_service.proto').TestService as ServiceClientConstructor; + server = new grpc.Server(); + server.addService(Client.service, { + unary: () => {}, + clientStream: () => {}, + serverStream: () => {}, + bidiStream: () => {} + }); + proxyServer = new grpc.Server(); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + server.start(); + client = new Client(`localhost:${port}`, grpc.credentials.createInsecure()); + proxyServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, proxyPort) => { + if (error) { + done(error); + return; + } + proxyServer.start(); + proxyClient = new Client(`localhost:${proxyPort}`, grpc.credentials.createInsecure()); + done(); + }); + }); + }); + afterEach(() => { + proxyServer.removeService(Client.service); + }); + after(() => { + server.forceShutdown(); + proxyServer.forceShutdown(); + }); + describe('Cancellation', () => { + it('should work with unary requests', (done) => { + done = multiDone(done, 2); + let call: grpc.ClientUnaryCall; + proxyServer.addService(Client.service, { + unary: (parent: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + client.unary(parent.request, {parent: parent}, (error: grpc.ServiceError, value: unknown) => { + callback(error, value); + assert(error); + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + }); + /* Cancel the original call after the server starts processing it to + * ensure that it does reach the server. */ + call.cancel(); + } + }); + call = proxyClient.unary({}, (error: grpc.ServiceError, value: unknown) => { + assert(error); + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + }); + }); + it('Should work with client streaming requests', (done) => { + done = multiDone(done, 2); + let call: grpc.ClientWritableStream; + proxyServer.addService(Client.service, { + clientStream: (parent: grpc.ServerReadableStream, callback: grpc.sendUnaryData) => { + client.clientStream({parent: parent}, (error: grpc.ServiceError, value: unknown) => { + callback(error, value); + assert(error); + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + }); + /* Cancel the original call after the server starts processing it to + * ensure that it does reach the server. */ + call.cancel(); + } + }); + call = proxyClient.clientStream((error: grpc.ServiceError, value: unknown) => { + assert(error); + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + }); + }); + it('Should work with server streaming requests', (done) => { + done = multiDone(done, 2); + let call: grpc.ClientReadableStream; + proxyServer.addService(Client.service, { + serverStream: (parent: grpc.ServerWritableStream) => { + const child = client.serverStream(parent.request, {parent: parent}); + child.on('error', () => {}); + child.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.CANCELLED); + done(); + }); + call.cancel(); + } + }); + call = proxyClient.serverStream({}); + call.on('error', () => {}); + call.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.CANCELLED); + done(); + }); + }); + it('Should work with bidi streaming requests', (done) => { + done = multiDone(done, 2); + let call: grpc.ClientDuplexStream; + proxyServer.addService(Client.service, { + bidiStream: (parent: grpc.ServerDuplexStream) => { + const child = client.bidiStream({parent: parent}); + child.on('error', () => {}); + child.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.CANCELLED); + done(); + }); + call.cancel(); + } + }); + call = proxyClient.bidiStream(); + call.on('error', () => {}); + call.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.CANCELLED); + done(); + }); + }); + }); + describe('Deadlines', () => { + it('should work with unary requests', (done) => { + done = multiDone(done, 2); + proxyServer.addService(Client.service, { + unary: (parent: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + client.unary(parent.request, {parent: parent, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { + callback(error, value); + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + } + }); + const deadline = new Date(); + deadline.setMilliseconds(deadline.getMilliseconds() + 100); + proxyClient.unary({}, {deadline}, (error: grpc.ServiceError, value: unknown) => { + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); + it('Should work with client streaming requests', (done) => { + done = multiDone(done, 2); + proxyServer.addService(Client.service, { + clientStream: (parent: grpc.ServerReadableStream, callback: grpc.sendUnaryData) => { + client.clientStream({parent: parent, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { + callback(error, value); + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + } + }); + const deadline = new Date(); + deadline.setMilliseconds(deadline.getMilliseconds() + 100); + proxyClient.clientStream({deadline, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); + it('Should work with server streaming requests', (done) => { + done = multiDone(done, 2); + let call: grpc.ClientReadableStream; + proxyServer.addService(Client.service, { + serverStream: (parent: grpc.ServerWritableStream) => { + const child = client.serverStream(parent.request, {parent: parent, propagate_flags: grpc.propagate.DEADLINE}); + child.on('error', () => {}); + child.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + } + }); + const deadline = new Date(); + deadline.setMilliseconds(deadline.getMilliseconds() + 100); + call = proxyClient.serverStream({}, {deadline}); + call.on('error', () => {}); + call.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); + it('Should work with bidi streaming requests', (done) => { + done = multiDone(done, 2); + let call: grpc.ClientDuplexStream; + proxyServer.addService(Client.service, { + bidiStream: (parent: grpc.ServerDuplexStream) => { + const child = client.bidiStream({parent: parent, propagate_flags: grpc.propagate.DEADLINE}); + child.on('error', () => {}); + child.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + } + }); + const deadline = new Date(); + deadline.setMilliseconds(deadline.getMilliseconds() + 100); + call = proxyClient.bidiStream({deadline}); + call.on('error', () => {}); + call.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); + }); +}); diff --git a/packages/grpc-js/test/test-channel-credentials.ts b/packages/grpc-js/test/test-channel-credentials.ts new file mode 100644 index 000000000..62e4c19a1 --- /dev/null +++ b/packages/grpc-js/test/test-channel-credentials.ts @@ -0,0 +1,209 @@ +/* + * Copyright 2019 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. + * + */ + +import * as assert from 'assert'; +import * as fs from 'fs'; +import * as path from 'path'; +import { promisify } from 'util'; + +import { CallCredentials } from '../src/call-credentials'; +import { ChannelCredentials } from '../src/channel-credentials'; +import * as grpc from '../src'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; + +import { assert2, loadProtoFile, mockFunction } from './common'; +import { sendUnaryData, ServerUnaryCall, ServiceError } from '../src'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const echoService = loadProtoFile(protoFile).EchoService as ServiceClientConstructor; + +class CallCredentialsMock implements CallCredentials { + child: CallCredentialsMock | null = null; + constructor(child?: CallCredentialsMock) { + if (child) { + this.child = child; + } + } + + generateMetadata = mockFunction; + + compose(callCredentials: CallCredentialsMock): CallCredentialsMock { + return new CallCredentialsMock(callCredentials); + } + + _equals(other: CallCredentialsMock): boolean { + if (!this.child) { + return this === other; + } else if (!other || !other.child) { + return false; + } else { + return this.child._equals(other.child); + } + } +} + +// tslint:disable-next-line:no-any +const readFile: (...args: any[]) => Promise = promisify(fs.readFile); +// A promise which resolves to loaded files in the form { ca, key, cert } +const pFixtures = Promise.all( + ['ca.pem', 'server1.key', 'server1.pem'].map(file => + readFile(`${__dirname}/fixtures/${file}`) + ) +).then(result => { + return { ca: result[0], key: result[1], cert: result[2] }; +}); + +describe('ChannelCredentials Implementation', () => { + describe('createInsecure', () => { + it('should return a ChannelCredentials object with no associated secure context', () => { + const creds = assert2.noThrowAndReturn(() => + ChannelCredentials.createInsecure() + ); + assert.ok(!creds._getConnectionOptions()); + }); + }); + + describe('createSsl', () => { + it('should work when given no arguments', () => { + const creds: ChannelCredentials = assert2.noThrowAndReturn(() => + ChannelCredentials.createSsl() + ); + assert.ok(!!creds._getConnectionOptions()); + }); + + it('should work with just a CA override', async () => { + const { ca } = await pFixtures; + const creds = assert2.noThrowAndReturn(() => + ChannelCredentials.createSsl(ca) + ); + assert.ok(!!creds._getConnectionOptions()); + }); + + it('should work with just a private key and cert chain', async () => { + const { key, cert } = await pFixtures; + const creds = assert2.noThrowAndReturn(() => + ChannelCredentials.createSsl(null, key, cert) + ); + assert.ok(!!creds._getConnectionOptions()); + }); + + it('should work with three parameters specified', async () => { + const { ca, key, cert } = await pFixtures; + const creds = assert2.noThrowAndReturn(() => + ChannelCredentials.createSsl(ca, key, cert) + ); + assert.ok(!!creds._getConnectionOptions()); + }); + + it('should throw if just one of private key and cert chain are missing', async () => { + const { ca, key, cert } = await pFixtures; + assert.throws(() => ChannelCredentials.createSsl(ca, key)); + assert.throws(() => ChannelCredentials.createSsl(ca, key, null)); + assert.throws(() => ChannelCredentials.createSsl(ca, null, cert)); + assert.throws(() => ChannelCredentials.createSsl(null, key)); + assert.throws(() => ChannelCredentials.createSsl(null, key, null)); + assert.throws(() => ChannelCredentials.createSsl(null, null, cert)); + }); + }); + + describe('compose', () => { + it('should return a ChannelCredentials object', () => { + const channelCreds = ChannelCredentials.createSsl(); + const callCreds = new CallCredentialsMock(); + const composedChannelCreds = channelCreds.compose(callCreds); + assert.strictEqual(composedChannelCreds._getCallCredentials(), callCreds); + }); + + it('should be chainable', () => { + const callCreds1 = new CallCredentialsMock(); + const callCreds2 = new CallCredentialsMock(); + // Associate both call credentials with channelCreds + const composedChannelCreds = ChannelCredentials.createSsl() + .compose(callCreds1) + .compose(callCreds2); + // Build a mock object that should be an identical copy + const composedCallCreds = callCreds1.compose(callCreds2); + assert.ok( + composedCallCreds._equals( + composedChannelCreds._getCallCredentials() as CallCredentialsMock + ) + ); + }); + }); +}); + +describe('ChannelCredentials usage', () => { + let client: ServiceClient; + let server: grpc.Server; + before(async () => { + const {ca, key, cert} = await pFixtures; + const serverCreds = grpc.ServerCredentials.createSsl(null, [{private_key: key, cert_chain: cert}]); + const channelCreds = ChannelCredentials.createSsl(ca); + const callCreds = CallCredentials.createFromMetadataGenerator((options, cb) => { + const metadata = new grpc.Metadata(); + metadata.set('test-key', 'test-value'); + cb(null, metadata); + }); + const combinedCreds = channelCreds.compose(callCreds); + return new Promise((resolve, reject) => { + + server = new grpc.Server(); + server.addService(echoService.service, { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + call.sendMetadata(call.metadata); + callback(null, call.request); + }, + }); + + server.bindAsync( + 'localhost:0', + serverCreds, + (err, port) => { + if (err) { + reject(err); + return; + } + client = new echoService( + `localhost:${port}`, + combinedCreds, + {'grpc.ssl_target_name_override': 'foo.test.google.fr', 'grpc.default_authority': 'foo.test.google.fr'} + ); + server.start(); + resolve(); + } + ); + }); + }); + after(() => { + server.forceShutdown(); + }); + + it('Should send the metadata from call credentials attached to channel credentials', (done) => { + const call = client.echo( + { value: 'test value', value2: 3 }, + assert2.mustCall((error: ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + }) + ); + call.on('metadata', assert2.mustCall((metadata: grpc.Metadata) => { + assert.deepStrictEqual(metadata.get('test-key'), ['test-value']); + + })); + assert2.afterMustCallsSatisfied(done); + }); +}); diff --git a/packages/grpc-js/test/test-channelz.ts b/packages/grpc-js/test/test-channelz.ts new file mode 100644 index 000000000..26c15d47d --- /dev/null +++ b/packages/grpc-js/test/test-channelz.ts @@ -0,0 +1,319 @@ +/* + * Copyright 2019 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. + * + */ + +import * as assert from 'assert'; +import * as protoLoader from '@grpc/proto-loader'; +import * as grpc from '../src'; + +import { ProtoGrpcType } from '../src/generated/channelz' +import { ChannelzClient } from '../src/generated/grpc/channelz/v1/Channelz'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; +import { loadProtoFile } from './common'; + +const loadedChannelzProto = protoLoader.loadSync('channelz.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + `${__dirname}/../../proto` + ] +}); +const channelzGrpcObject = grpc.loadPackageDefinition(loadedChannelzProto) as unknown as ProtoGrpcType; + +const TestServiceClient = loadProtoFile(`${__dirname}/fixtures/test_service.proto`).TestService as ServiceClientConstructor; + +const testServiceImpl: grpc.UntypedServiceImplementation = { + unary(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) { + if (call.request.error) { + setTimeout(() => { + callback({ + code: grpc.status.INVALID_ARGUMENT, + details: call.request.message + }); + }, call.request.errorAfter) + } else { + callback(null, {count: 1}); + } + } +} + +describe('Channelz', () => { + let channelzServer: grpc.Server; + let channelzClient: ChannelzClient; + let testServer: grpc.Server; + let testClient: ServiceClient; + + before((done) => { + channelzServer = new grpc.Server(); + channelzServer.addService(grpc.getChannelzServiceDefinition(), grpc.getChannelzHandlers()); + channelzServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + channelzServer.start(); + channelzClient = new channelzGrpcObject.grpc.channelz.v1.Channelz(`localhost:${port}`, grpc.credentials.createInsecure()); + done(); + }); + }); + + after(() => { + channelzClient.close(); + channelzServer.forceShutdown(); + }); + + beforeEach((done) => { + testServer = new grpc.Server(); + testServer.addService(TestServiceClient.service, testServiceImpl); + testServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + testServer.start(); + testClient = new TestServiceClient(`localhost:${port}`, grpc.credentials.createInsecure()); + done(); + }); + }); + + afterEach(() => { + testClient.close(); + testServer.forceShutdown(); + }); + + it('should see a newly created channel', (done) => { + // Test that the specific test client channel info can be retrieved + channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, result) => { + assert.ifError(error); + assert(result); + assert(result.channel); + assert(result.channel.ref); + assert.strictEqual(+result.channel.ref.channel_id, testClient.getChannel().getChannelzRef().id); + // Test that the channel is in the list of top channels + channelzClient.getTopChannels({start_channel_id: testClient.getChannel().getChannelzRef().id, max_results:1}, (error, result) => { + assert.ifError(error); + assert(result); + assert.strictEqual(result.channel.length, 1); + assert(result.channel[0].ref); + assert.strictEqual(+result.channel[0].ref.channel_id, testClient.getChannel().getChannelzRef().id); + done(); + }); + }); + }); + + it('should see a newly created server', (done) => { + // Test that the specific test server info can be retrieved + channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, result) => { + assert.ifError(error); + assert(result); + assert(result.server); + assert(result.server.ref); + assert.strictEqual(+result.server.ref.server_id, testServer.getChannelzRef().id); + // Test that the server is in the list of servers + channelzClient.getServers({start_server_id: testServer.getChannelzRef().id, max_results: 1}, (error, result) => { + assert.ifError(error); + assert(result); + assert.strictEqual(result.server.length, 1); + assert(result.server[0].ref); + assert.strictEqual(+result.server[0].ref.server_id, testServer.getChannelzRef().id); + done(); + }); + }); + }); + + it('should count successful calls', (done) => { + testClient.unary({}, (error: grpc.ServiceError, value: unknown) => { + assert.ifError(error); + // Channel data tests + channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, channelResult) => { + assert.ifError(error); + assert(channelResult); + assert(channelResult.channel); + assert(channelResult.channel.ref); + assert(channelResult.channel.data); + assert.strictEqual(+channelResult.channel.data.calls_started, 1); + assert.strictEqual(+channelResult.channel.data.calls_succeeded, 1); + assert.strictEqual(+channelResult.channel.data.calls_failed, 0); + assert.strictEqual(channelResult.channel.subchannel_ref.length, 1); + channelzClient.getSubchannel({subchannel_id: channelResult.channel.subchannel_ref[0].subchannel_id}, (error, subchannelResult) => { + assert.ifError(error); + assert(subchannelResult); + assert(subchannelResult.subchannel); + assert(subchannelResult.subchannel.ref); + assert(subchannelResult.subchannel.data); + assert.strictEqual(subchannelResult.subchannel.ref.subchannel_id, channelResult.channel!.subchannel_ref[0].subchannel_id); + assert.strictEqual(+subchannelResult.subchannel.data.calls_started, 1); + assert.strictEqual(+subchannelResult.subchannel.data.calls_succeeded, 1); + assert.strictEqual(+subchannelResult.subchannel.data.calls_failed, 0); + assert.strictEqual(subchannelResult.subchannel.socket_ref.length, 1); + channelzClient.getSocket({socket_id: subchannelResult.subchannel.socket_ref[0].socket_id}, (error, socketResult) => { + assert.ifError(error); + assert(socketResult); + assert(socketResult.socket); + assert(socketResult.socket.ref); + assert(socketResult.socket.data); + assert.strictEqual(socketResult.socket.ref.socket_id, subchannelResult.subchannel!.socket_ref[0].socket_id); + assert.strictEqual(+socketResult.socket.data.streams_started, 1); + assert.strictEqual(+socketResult.socket.data.streams_succeeded, 1); + assert.strictEqual(+socketResult.socket.data.streams_failed, 0); + assert.strictEqual(+socketResult.socket.data.messages_received, 1); + assert.strictEqual(+socketResult.socket.data.messages_sent, 1); + // Server data tests + channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, serverResult) => { + assert.ifError(error); + assert(serverResult); + assert(serverResult.server); + assert(serverResult.server.ref); + assert(serverResult.server.data); + assert.strictEqual(+serverResult.server.ref.server_id, testServer.getChannelzRef().id); + assert.strictEqual(+serverResult.server.data.calls_started, 1); + assert.strictEqual(+serverResult.server.data.calls_succeeded, 1); + assert.strictEqual(+serverResult.server.data.calls_failed, 0); + channelzClient.getServerSockets({server_id: testServer.getChannelzRef().id}, (error, socketsResult) => { + assert.ifError(error); + assert(socketsResult); + assert.strictEqual(socketsResult.socket_ref.length, 1); + channelzClient.getSocket({socket_id: socketsResult.socket_ref[0].socket_id}, (error, serverSocketResult) => { + assert.ifError(error); + assert(serverSocketResult); + assert(serverSocketResult.socket); + assert(serverSocketResult.socket.ref); + assert(serverSocketResult.socket.data); + assert.strictEqual(serverSocketResult.socket.ref.socket_id, socketsResult.socket_ref[0].socket_id); + assert.strictEqual(+serverSocketResult.socket.data.streams_started, 1); + assert.strictEqual(+serverSocketResult.socket.data.streams_succeeded, 1); + assert.strictEqual(+serverSocketResult.socket.data.streams_failed, 0); + assert.strictEqual(+serverSocketResult.socket.data.messages_received, 1); + assert.strictEqual(+serverSocketResult.socket.data.messages_sent, 1); + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('should count failed calls', (done) => { + testClient.unary({error: true}, (error: grpc.ServiceError, value: unknown) => { + assert(error); + // Channel data tests + channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, channelResult) => { + assert.ifError(error); + assert(channelResult); + assert(channelResult.channel); + assert(channelResult.channel.ref); + assert(channelResult.channel.data); + assert.strictEqual(+channelResult.channel.data.calls_started, 1); + assert.strictEqual(+channelResult.channel.data.calls_succeeded, 0); + assert.strictEqual(+channelResult.channel.data.calls_failed, 1); + assert.strictEqual(channelResult.channel.subchannel_ref.length, 1); + channelzClient.getSubchannel({subchannel_id: channelResult.channel.subchannel_ref[0].subchannel_id}, (error, subchannelResult) => { + assert.ifError(error); + assert(subchannelResult); + assert(subchannelResult.subchannel); + assert(subchannelResult.subchannel.ref); + assert(subchannelResult.subchannel.data); + assert.strictEqual(subchannelResult.subchannel.ref.subchannel_id, channelResult.channel!.subchannel_ref[0].subchannel_id); + assert.strictEqual(+subchannelResult.subchannel.data.calls_started, 1); + assert.strictEqual(+subchannelResult.subchannel.data.calls_succeeded, 0); + assert.strictEqual(+subchannelResult.subchannel.data.calls_failed, 1); + assert.strictEqual(subchannelResult.subchannel.socket_ref.length, 1); + channelzClient.getSocket({socket_id: subchannelResult.subchannel.socket_ref[0].socket_id}, (error, socketResult) => { + assert.ifError(error); + assert(socketResult); + assert(socketResult.socket); + assert(socketResult.socket.ref); + assert(socketResult.socket.data); + assert.strictEqual(socketResult.socket.ref.socket_id, subchannelResult.subchannel!.socket_ref[0].socket_id); + assert.strictEqual(+socketResult.socket.data.streams_started, 1); + assert.strictEqual(+socketResult.socket.data.streams_succeeded, 1); + assert.strictEqual(+socketResult.socket.data.streams_failed, 0); + assert.strictEqual(+socketResult.socket.data.messages_received, 0); + assert.strictEqual(+socketResult.socket.data.messages_sent, 1); + // Server data tests + channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, serverResult) => { + assert.ifError(error); + assert(serverResult); + assert(serverResult.server); + assert(serverResult.server.ref); + assert(serverResult.server.data); + assert.strictEqual(+serverResult.server.ref.server_id, testServer.getChannelzRef().id); + assert.strictEqual(+serverResult.server.data.calls_started, 1); + assert.strictEqual(+serverResult.server.data.calls_succeeded, 0); + assert.strictEqual(+serverResult.server.data.calls_failed, 1); + channelzClient.getServerSockets({server_id: testServer.getChannelzRef().id}, (error, socketsResult) => { + assert.ifError(error); + assert(socketsResult); + assert.strictEqual(socketsResult.socket_ref.length, 1); + channelzClient.getSocket({socket_id: socketsResult.socket_ref[0].socket_id}, (error, serverSocketResult) => { + assert.ifError(error); + assert(serverSocketResult); + assert(serverSocketResult.socket); + assert(serverSocketResult.socket.ref); + assert(serverSocketResult.socket.data); + assert.strictEqual(serverSocketResult.socket.ref.socket_id, socketsResult.socket_ref[0].socket_id); + assert.strictEqual(+serverSocketResult.socket.data.streams_started, 1); + assert.strictEqual(+serverSocketResult.socket.data.streams_succeeded, 0); + assert.strictEqual(+serverSocketResult.socket.data.streams_failed, 1); + assert.strictEqual(+serverSocketResult.socket.data.messages_received, 1); + assert.strictEqual(+serverSocketResult.socket.data.messages_sent, 0); + done(); + }); + }); + }); + }); + }); + }); + }); + }); +}); + +describe('Disabling channelz', () => { + let testServer: grpc.Server; + let testClient: ServiceClient; + beforeEach((done) => { + testServer = new grpc.Server({'grpc.enable_channelz': 0}); + testServer.addService(TestServiceClient.service, testServiceImpl); + testServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + testServer.start(); + testClient = new TestServiceClient(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.enable_channelz': 0}); + done(); + }); + }); + + afterEach(() => { + testClient.close(); + testServer.forceShutdown(); + }); + + it('Should still work', (done) => { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 1); + testClient.unary({}, {deadline}, (error: grpc.ServiceError, value: unknown) => { + assert.ifError(error); + done(); + }); + }); +}); diff --git a/packages/grpc-js/test/test-client.ts b/packages/grpc-js/test/test-client.ts new file mode 100644 index 000000000..21dad99f1 --- /dev/null +++ b/packages/grpc-js/test/test-client.ts @@ -0,0 +1,119 @@ +/* + * Copyright 2019 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. + * + */ + +import * as assert from 'assert'; + +import * as grpc from '../src'; +import { Server, ServerCredentials } from '../src'; +import { Client } from '../src'; +import { ConnectivityState } from "../src/connectivity-state"; + +const clientInsecureCreds = grpc.credentials.createInsecure(); +const serverInsecureCreds = ServerCredentials.createInsecure(); + +describe('Client', () => { + let server: Server; + let client: Client; + + before(done => { + server = new Server(); + + server.bindAsync( + 'localhost:0', + serverInsecureCreds, + (err, port) => { + assert.ifError(err); + client = new Client( + `localhost:${port}`, + clientInsecureCreds + ); + server.start(); + done(); + } + ); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('should call the waitForReady callback only once, when channel connectivity state is READY', done => { + const deadline = Date.now() + 100; + let calledTimes = 0; + client.waitForReady(deadline, err => { + assert.ifError(err); + assert.equal( + client.getChannel().getConnectivityState(true), + ConnectivityState.READY + ); + calledTimes += 1; + }); + setTimeout(() => { + assert.equal(calledTimes, 1); + done(); + }, deadline - Date.now()); + }); +}); + +describe('Client without a server', () => { + let client: Client; + before(() => { + // Arbitrary target that should not have a running server + client = new Client('localhost:12345', clientInsecureCreds); + }); + after(() => { + client.close(); + }); + it('should fail multiple calls to the nonexistent server', function(done) { + this.timeout(5000); + // Regression test for https://github.com/grpc/grpc-node/issues/1411 + client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { + assert(error); + assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); + client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { + assert(error); + assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); + done(); + }); + }); + }); +}); + +describe('Client with a nonexistent target domain', () => { + let client: Client; + before(() => { + // DNS name that does not exist per RFC 6761 section 6.4 + client = new Client('host.invalid', clientInsecureCreds); + }); + after(() => { + client.close(); + }); + it('should fail multiple calls', function(done) { + this.timeout(5000); + // Regression test for https://github.com/grpc/grpc-node/issues/1411 + client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { + assert(error); + assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); + client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { + assert(error); + assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/grpc-js/test/test-deadline.ts b/packages/grpc-js/test/test-deadline.ts new file mode 100644 index 000000000..d117fc52a --- /dev/null +++ b/packages/grpc-js/test/test-deadline.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2021 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. + * + */ + +import * as assert from 'assert'; + +import * as grpc from '../src'; +import { experimental } from '../src'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; +import { loadProtoFile } from './common'; +import ServiceConfig = experimental.ServiceConfig; + +const TIMEOUT_SERVICE_CONFIG: ServiceConfig = { + loadBalancingConfig: [], + methodConfig: [{ + name: [ + {service: 'TestService'} + ], + timeout: { + seconds: 1, + nanos: 0 + } + }] +}; + +describe('Client with configured timeout', () => { + let server: grpc.Server; + let Client: ServiceClientConstructor; + let client: ServiceClient; + + before(done => { + Client = loadProtoFile(__dirname + '/fixtures/test_service.proto').TestService as ServiceClientConstructor; + server = new grpc.Server(); + server.addService(Client.service, { + unary: () => {}, + clientStream: () => {}, + serverStream: () => {}, + bidiStream: () => {} + }); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + server.start(); + client = new Client(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(TIMEOUT_SERVICE_CONFIG)}); + done(); + }); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('Should end calls without explicit deadline with DEADLINE_EXCEEDED', done => { + client.unary({}, (error: grpc.ServiceError, value: unknown) =>{ + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); + + it('Should end calls with a long explicit deadline with DEADLINE_EXCEEDED', done => { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 20); + client.unary({}, (error: grpc.ServiceError, value: unknown) =>{ + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); +}); diff --git a/packages/grpc-js/test/test-global-subchannel-pool.ts b/packages/grpc-js/test/test-global-subchannel-pool.ts new file mode 100644 index 000000000..999a11bf7 --- /dev/null +++ b/packages/grpc-js/test/test-global-subchannel-pool.ts @@ -0,0 +1,130 @@ +/* + * Copyright 2023 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. + * + */ + +import * as assert from 'assert'; +import * as path from 'path'; + +import * as grpc from '../src'; +import {sendUnaryData, Server, ServerCredentials, ServerUnaryCall, ServiceClientConstructor, ServiceError} from '../src'; + +import {loadProtoFile} from './common'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const echoService = + loadProtoFile(protoFile).EchoService as ServiceClientConstructor; + +describe('Global subchannel pool', () => { + let server: Server; + let serverPort: number; + + let client1: InstanceType; + let client2: InstanceType; + + let promises: Promise[]; + + before(done => { + server = new Server(); + server.addService(echoService.service, { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + callback(null, call.request); + }, + }); + + server.bindAsync( + 'localhost:0', ServerCredentials.createInsecure(), (err, port) => { + assert.ifError(err); + serverPort = port; + server.start(); + done(); + }); + }); + + beforeEach(() => { + promises = []; + }) + + after(done => { + server.tryShutdown(done); + }); + + function callService(client: InstanceType) { + return new Promise((resolve) => { + const request = {value: 'test value', value2: 3}; + + client.echo(request, (error: ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, request); + resolve(); + }); + }) + } + + function connect() { + const grpcOptions = { + 'grpc.use_local_subchannel_pool': 0, + } + + client1 = new echoService( + `127.0.0.1:${serverPort}`, grpc.credentials.createInsecure(), + grpcOptions); + + client2 = new echoService( + `127.0.0.1:${serverPort}`, grpc.credentials.createInsecure(), + grpcOptions); + } + + /* This is a regression test for a bug where client1.close in the + * waitForReady callback would cause the subchannel to transition to IDLE + * even though client2 is also using it. */ + it('Should handle client.close calls in waitForReady', + done => { + connect(); + + promises.push(new Promise((resolve) => { + client1.waitForReady(Date.now() + 50, (error) => { + assert.ifError(error); + client1.close(); + resolve(); + }); + })) + + promises.push(new Promise((resolve) => { + client2.waitForReady(Date.now() + 50, (error) => { + assert.ifError(error); + resolve(); + }); + })) + + Promise.all(promises).then(() => {done()}); + }) + + it('Call the service', done => { + promises.push(callService(client2)); + + Promise.all(promises).then(() => { + done(); + }); + }) + + it('Should complete the client lifecycle without error', done => { + setTimeout(() => { + client1.close(); + client2.close(); + done() + }, 500); + }); +}); diff --git a/packages/grpc-js/test/test-local-subchannel-pool.ts b/packages/grpc-js/test/test-local-subchannel-pool.ts new file mode 100644 index 000000000..081b2d3dc --- /dev/null +++ b/packages/grpc-js/test/test-local-subchannel-pool.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2022 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. + * + */ + +import * as assert from 'assert'; +import * as path from 'path'; +import * as grpc from '../src'; +import { sendUnaryData, Server, ServerCredentials, ServerUnaryCall, ServiceClientConstructor, ServiceError } from "../src"; +import { loadProtoFile } from './common'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const echoService = loadProtoFile(protoFile) + .EchoService as ServiceClientConstructor; + +describe('Local subchannel pool', () => { + let server: Server; + let serverPort: number; + + before(done => { + + server = new Server(); + server.addService(echoService.service, { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + callback(null, call.request); + }, + }); + + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + serverPort = port; + server.start(); + done(); + } + ); + }); + + after(done => { + server.tryShutdown(done); + }); + + it('should complete the client lifecycle without error', done => { + const client = new echoService( + `localhost:${serverPort}`, + grpc.credentials.createInsecure(), + {'grpc.use_local_subchannel_pool': 1} + ); + client.echo( + { value: 'test value', value2: 3 }, + (error: ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + client.close(); + done(); + } + ); + }); +}); \ No newline at end of file diff --git a/packages/grpc-js/test/test-logging.ts b/packages/grpc-js/test/test-logging.ts new file mode 100644 index 000000000..d275158cf --- /dev/null +++ b/packages/grpc-js/test/test-logging.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2019 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. + * + */ + +import * as assert from 'assert'; + +import * as grpc from '../src'; +import * as logging from '../src/logging'; + +describe('Logging', () => { + afterEach(() => { + // Ensure that the logger is restored to its defaults after each test. + grpc.setLogger(console); + grpc.setLogVerbosity(grpc.logVerbosity.DEBUG); + }); + + it('sets the logger to a new value', () => { + const logger: Partial = {}; + + logging.setLogger(logger); + assert.strictEqual(logging.getLogger(), logger); + }); + + it('gates logging based on severity', () => { + const output: Array = []; + const logger: Partial = { + error(...args: string[]): void { + output.push(args); + }, + }; + + logging.setLogger(logger); + + // The default verbosity (DEBUG) should log everything. + logging.log(grpc.logVerbosity.DEBUG, 'a', 'b', 'c'); + logging.log(grpc.logVerbosity.INFO, 'd', 'e'); + logging.log(grpc.logVerbosity.ERROR, 'f'); + + // The INFO verbosity should not log DEBUG data. + logging.setLoggerVerbosity(grpc.logVerbosity.INFO); + logging.log(grpc.logVerbosity.DEBUG, 1, 2, 3); + logging.log(grpc.logVerbosity.INFO, 'g'); + logging.log(grpc.logVerbosity.ERROR, 'h', 'i'); + + // The ERROR verbosity should not log DEBUG or INFO data. + logging.setLoggerVerbosity(grpc.logVerbosity.ERROR); + logging.log(grpc.logVerbosity.DEBUG, 4, 5, 6); + logging.log(grpc.logVerbosity.INFO, 7, 8); + logging.log(grpc.logVerbosity.ERROR, 'j', 'k'); + + assert.deepStrictEqual(output, [ + ['a', 'b', 'c'], + ['d', 'e'], + ['f'], + ['g'], + ['h', 'i'], + ['j', 'k'], + ]); + }); +}); diff --git a/packages/grpc-js-core/test/test-metadata.ts b/packages/grpc-js/test/test-metadata.ts similarity index 60% rename from packages/grpc-js-core/test/test-metadata.ts rename to packages/grpc-js/test/test-metadata.ts index 6e2275329..44182ef39 100644 --- a/packages/grpc-js-core/test/test-metadata.ts +++ b/packages/grpc-js/test/test-metadata.ts @@ -1,7 +1,24 @@ +/* + * Copyright 2019 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. + * + */ + import * as assert from 'assert'; import * as http2 from 'http2'; -import {range} from 'lodash'; -import {Metadata} from '../src/metadata'; +import { range } from 'lodash'; +import { Metadata, MetadataObject, MetadataValue } from '../src/metadata'; class TestMetadata extends Metadata { getInternalRepresentation() { @@ -11,14 +28,15 @@ class TestMetadata extends Metadata { static fromHttp2Headers(headers: http2.IncomingHttpHeaders): TestMetadata { const result = Metadata.fromHttp2Headers(headers) as TestMetadata; result.getInternalRepresentation = - TestMetadata.prototype.getInternalRepresentation; + TestMetadata.prototype.getInternalRepresentation; return result; } } const validKeyChars = '0123456789abcdefghijklmnopqrstuvwxyz_-.'; -const validNonBinValueChars = - range(0x20, 0x7f).map(code => String.fromCharCode(code)).join(''); +const validNonBinValueChars = range(0x20, 0x7f) + .map(code => String.fromCharCode(code)) + .join(''); describe('Metadata', () => { let metadata: TestMetadata; @@ -30,7 +48,7 @@ describe('Metadata', () => { describe('set', () => { it('Only accepts string values for non "-bin" keys', () => { assert.throws(() => { - metadata.set('key', new Buffer('value')); + metadata.set('key', Buffer.from('value')); }); assert.doesNotThrow(() => { metadata.set('key', 'value'); @@ -42,7 +60,7 @@ describe('Metadata', () => { metadata.set('key-bin', 'value'); }); assert.doesNotThrow(() => { - metadata.set('key-bin', new Buffer('value')); + metadata.set('key-bin', Buffer.from('value')); }); }); @@ -52,7 +70,7 @@ describe('Metadata', () => { }); assert.throws(() => { metadata.set('key$', 'value'); - }); + }, /Error: Metadata key "key\$" contains illegal characters/); assert.throws(() => { metadata.set('', 'value'); }); @@ -69,27 +87,27 @@ describe('Metadata', () => { it('Saves values that can be retrieved', () => { metadata.set('key', 'value'); - assert.deepEqual(metadata.get('key'), ['value']); + assert.deepStrictEqual(metadata.get('key'), ['value']); }); it('Overwrites previous values', () => { metadata.set('key', 'value1'); metadata.set('key', 'value2'); - assert.deepEqual(metadata.get('key'), ['value2']); + assert.deepStrictEqual(metadata.get('key'), ['value2']); }); it('Normalizes keys', () => { metadata.set('Key', 'value1'); - assert.deepEqual(metadata.get('key'), ['value1']); + assert.deepStrictEqual(metadata.get('key'), ['value1']); metadata.set('KEY', 'value2'); - assert.deepEqual(metadata.get('key'), ['value2']); + assert.deepStrictEqual(metadata.get('key'), ['value2']); }); }); describe('add', () => { it('Only accepts string values for non "-bin" keys', () => { assert.throws(() => { - metadata.add('key', new Buffer('value')); + metadata.add('key', Buffer.from('value')); }); assert.doesNotThrow(() => { metadata.add('key', 'value'); @@ -101,7 +119,7 @@ describe('Metadata', () => { metadata.add('key-bin', 'value'); }); assert.doesNotThrow(() => { - metadata.add('key-bin', new Buffer('value')); + metadata.add('key-bin', Buffer.from('value')); }); }); @@ -116,20 +134,20 @@ describe('Metadata', () => { it('Saves values that can be retrieved', () => { metadata.add('key', 'value'); - assert.deepEqual(metadata.get('key'), ['value']); + assert.deepStrictEqual(metadata.get('key'), ['value']); }); it('Combines with previous values', () => { metadata.add('key', 'value1'); metadata.add('key', 'value2'); - assert.deepEqual(metadata.get('key'), ['value1', 'value2']); + assert.deepStrictEqual(metadata.get('key'), ['value1', 'value2']); }); it('Normalizes keys', () => { metadata.add('Key', 'value1'); - assert.deepEqual(metadata.get('key'), ['value1']); + assert.deepStrictEqual(metadata.get('key'), ['value1']); metadata.add('KEY', 'value2'); - assert.deepEqual(metadata.get('key'), ['value1', 'value2']); + assert.deepStrictEqual(metadata.get('key'), ['value1', 'value2']); }); }); @@ -137,13 +155,13 @@ describe('Metadata', () => { it('clears values from a key', () => { metadata.add('key', 'value'); metadata.remove('key'); - assert.deepEqual(metadata.get('key'), []); + assert.deepStrictEqual(metadata.get('key'), []); }); it('Normalizes keys', () => { metadata.add('key', 'value'); metadata.remove('KEY'); - assert.deepEqual(metadata.get('key'), []); + assert.deepStrictEqual(metadata.get('key'), []); }); }); @@ -151,19 +169,19 @@ describe('Metadata', () => { beforeEach(() => { metadata.add('key', 'value1'); metadata.add('key', 'value2'); - metadata.add('key-bin', new Buffer('value')); + metadata.add('key-bin', Buffer.from('value')); }); it('gets all values associated with a key', () => { - assert.deepEqual(metadata.get('key'), ['value1', 'value2']); + assert.deepStrictEqual(metadata.get('key'), ['value1', 'value2']); }); it('Normalizes keys', () => { - assert.deepEqual(metadata.get('KEY'), ['value1', 'value2']); + assert.deepStrictEqual(metadata.get('KEY'), ['value1', 'value2']); }); it('returns an empty list for non-existent keys', () => { - assert.deepEqual(metadata.get('non-existent-key'), []); + assert.deepStrictEqual(metadata.get('non-existent-key'), []); }); it('returns Buffers for "-bin" keys', () => { @@ -177,8 +195,11 @@ describe('Metadata', () => { metadata.add('Key2', 'value2'); metadata.add('KEY3', 'value3a'); metadata.add('KEY3', 'value3b'); - assert.deepEqual( - metadata.getMap(), {key1: 'value1', key2: 'value2', key3: 'value3a'}); + assert.deepStrictEqual(metadata.getMap(), { + key1: 'value1', + key2: 'value2', + key3: 'value3a', + }); }); }); @@ -186,21 +207,31 @@ describe('Metadata', () => { it('retains values from the original', () => { metadata.add('key', 'value'); const copy = metadata.clone(); - assert.deepEqual(copy.get('key'), ['value']); + assert.deepStrictEqual(copy.get('key'), ['value']); }); it('Does not see newly added values', () => { metadata.add('key', 'value1'); const copy = metadata.clone(); metadata.add('key', 'value2'); - assert.deepEqual(copy.get('key'), ['value1']); + assert.deepStrictEqual(copy.get('key'), ['value1']); }); it('Does not add new values to the original', () => { metadata.add('key', 'value1'); const copy = metadata.clone(); copy.add('key', 'value2'); - assert.deepEqual(metadata.get('key'), ['value1']); + assert.deepStrictEqual(metadata.get('key'), ['value1']); + }); + + it('Copy cannot modify binary values in the original', () => { + const buf = Buffer.from('value-bin'); + metadata.add('key-bin', buf); + const copy = metadata.clone(); + const copyBuf = copy.get('key-bin')[0] as Buffer; + assert.deepStrictEqual(copyBuf, buf); + copyBuf.fill(0); + assert.notDeepStrictEqual(copyBuf, buf); }); }); @@ -219,12 +250,15 @@ describe('Metadata', () => { const metadata2IR = metadata2.getInternalRepresentation(); metadata.merge(metadata2); // Ensure metadata2 didn't change - assert.deepEqual(metadata2.getInternalRepresentation(), metadata2IR); - assert.deepEqual(metadata.get('key1'), ['value1', 'value1']); - assert.deepEqual(metadata.get('key2'), ['value2a', 'value2b']); - assert.deepEqual(metadata.get('key3'), ['value3a', 'value3b']); - assert.deepEqual(metadata.get('key4'), ['value4']); - assert.deepEqual(metadata.get('key5'), ['value5a', 'value5b']); + assert.deepStrictEqual( + metadata2.getInternalRepresentation(), + metadata2IR + ); + assert.deepStrictEqual(metadata.get('key1'), ['value1', 'value1']); + assert.deepStrictEqual(metadata.get('key2'), ['value2a', 'value2b']); + assert.deepStrictEqual(metadata.get('key3'), ['value3a', 'value3b']); + assert.deepStrictEqual(metadata.get('key4'), ['value4']); + assert.deepStrictEqual(metadata.get('key5'), ['value5a', 'value5b']); }); }); @@ -238,19 +272,20 @@ describe('Metadata', () => { metadata.add('key-bin', Buffer.from(range(16, 32))); metadata.add('key-bin', Buffer.from(range(0, 32))); const headers = metadata.toHttp2Headers(); - assert.deepEqual(headers, { + assert.deepStrictEqual(headers, { key1: ['value1'], key2: ['value2'], key3: ['value3a', 'value3b'], 'key-bin': [ - 'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==', - 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=' - ] + 'AAECAwQFBgcICQoLDA0ODw==', + 'EBESExQVFhcYGRobHB0eHw==', + 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=', + ], }); }); it('creates an empty header object from empty Metadata', () => { - assert.deepEqual(metadata.toHttp2Headers(), {}); + assert.deepStrictEqual(metadata.toHttp2Headers(), {}); }); }); @@ -260,28 +295,36 @@ describe('Metadata', () => { key1: 'value1', key2: ['value2'], key3: ['value3a', 'value3b'], + key4: ['part1, part2'], 'key-bin': [ - 'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==', - 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=' - ] + 'AAECAwQFBgcICQoLDA0ODw==', + 'EBESExQVFhcYGRobHB0eHw==', + 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=', + ], }; const metadataFromHeaders = TestMetadata.fromHttp2Headers(headers); const internalRepr = metadataFromHeaders.getInternalRepresentation(); - assert.deepEqual(internalRepr, { - key1: ['value1'], - key2: ['value2'], - key3: ['value3a', 'value3b'], - 'key-bin': [ - Buffer.from(range(0, 16)), Buffer.from(range(16, 32)), - Buffer.from(range(0, 32)) - ] - }); + const expected: MetadataObject = new Map([ + ['key1', ['value1']], + ['key2', ['value2']], + ['key3', ['value3a', 'value3b']], + ['key4', ['part1, part2']], + [ + 'key-bin', + [ + Buffer.from(range(0, 16)), + Buffer.from(range(16, 32)), + Buffer.from(range(0, 32)), + ], + ], + ]); + assert.deepStrictEqual(internalRepr, expected); }); it('creates an empty Metadata object from empty headers', () => { const metadataFromHeaders = TestMetadata.fromHttp2Headers({}); const internalRepr = metadataFromHeaders.getInternalRepresentation(); - assert.deepEqual(internalRepr, {}); + assert.deepStrictEqual(internalRepr, new Map()); }); }); }); diff --git a/packages/grpc-js/test/test-outlier-detection.ts b/packages/grpc-js/test/test-outlier-detection.ts new file mode 100644 index 000000000..a91350fd7 --- /dev/null +++ b/packages/grpc-js/test/test-outlier-detection.ts @@ -0,0 +1,545 @@ +/* + * Copyright 2022 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. + * + */ + +import * as assert from 'assert'; +import * as path from 'path'; +import * as grpc from '../src'; +import { loadProtoFile } from './common'; +import { OutlierDetectionLoadBalancingConfig } from '../src/load-balancer-outlier-detection' + +function multiDone(done: Mocha.Done, target: number) { + let count = 0; + return (error?: any) => { + if (error) { + done(error); + } + count++; + if (count >= target) { + done(); + } + } +} + +const defaultOutlierDetectionServiceConfig = { + methodConfig: [], + loadBalancingConfig: [ + { + outlier_detection: { + success_rate_ejection: {}, + failure_percentage_ejection: {}, + child_policy: [{round_robin: {}}] + } + } + ] +}; + +const defaultOutlierDetectionServiceConfigString = JSON.stringify(defaultOutlierDetectionServiceConfig); + +const successRateOutlierDetectionServiceConfig = { + methodConfig: [], + loadBalancingConfig: [ + { + outlier_detection: { + interval: { + seconds: 1, + nanos: 0 + }, + base_ejection_time: { + seconds: 3, + nanos: 0 + }, + success_rate_ejection: { + request_volume: 5 + }, + child_policy: [{round_robin: {}}] + } + } + ] +}; + +const successRateOutlierDetectionServiceConfigString = JSON.stringify(successRateOutlierDetectionServiceConfig); + +const failurePercentageOutlierDetectionServiceConfig = { + methodConfig: [], + loadBalancingConfig: [ + { + outlier_detection: { + interval: { + seconds: 1, + nanos: 0 + }, + base_ejection_time: { + seconds: 3, + nanos: 0 + }, + failure_percentage_ejection: { + request_volume: 5 + }, + child_policy: [{round_robin: {}}] + } + } + ] +}; + +const falurePercentageOutlierDetectionServiceConfigString = JSON.stringify(failurePercentageOutlierDetectionServiceConfig); + +const goodService = { + echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + callback(null, call.request) + } +}; + +const badService = { + echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + callback({ + code: grpc.status.PERMISSION_DENIED, + details: 'Permission denied' + }) + } +} + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const EchoService = loadProtoFile(protoFile) + .EchoService as grpc.ServiceClientConstructor; + +describe('Outlier detection config validation', () => { + describe('interval', () => { + it('Should reject a negative interval', () => { + const loadBalancingConfig = { + interval: { + seconds: -1, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /interval parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large interval', () => { + const loadBalancingConfig = { + interval: { + seconds: 1e12, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /interval parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a negative interval.nanos', () => { + const loadBalancingConfig = { + interval: { + seconds: 0, + nanos: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /interval parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large interval.nanos', () => { + const loadBalancingConfig = { + interval: { + seconds: 0, + nanos: 1e12 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /interval parse error: values out of range for non-negative Duaration/); + }); + }); + describe('base_ejection_time', () => { + it('Should reject a negative base_ejection_time', () => { + const loadBalancingConfig = { + base_ejection_time: { + seconds: -1, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /base_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large base_ejection_time', () => { + const loadBalancingConfig = { + base_ejection_time: { + seconds: 1e12, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /base_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a negative base_ejection_time.nanos', () => { + const loadBalancingConfig = { + base_ejection_time: { + seconds: 0, + nanos: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /base_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large base_ejection_time.nanos', () => { + const loadBalancingConfig = { + base_ejection_time: { + seconds: 0, + nanos: 1e12 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /base_ejection_time parse error: values out of range for non-negative Duaration/); + }); + }); + describe('max_ejection_time', () => { + it('Should reject a negative max_ejection_time', () => { + const loadBalancingConfig = { + max_ejection_time: { + seconds: -1, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large max_ejection_time', () => { + const loadBalancingConfig = { + max_ejection_time: { + seconds: 1e12, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a negative max_ejection_time.nanos', () => { + const loadBalancingConfig = { + max_ejection_time: { + seconds: 0, + nanos: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large max_ejection_time.nanos', () => { + const loadBalancingConfig = { + max_ejection_time: { + seconds: 0, + nanos: 1e12 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_time parse error: values out of range for non-negative Duaration/); + }); + }); + describe('max_ejection_percent', () => { + it('Should reject a value above 100', () => { + const loadBalancingConfig = { + max_ejection_percent: 101, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_percent parse error: value out of range for percentage/); + }); + it('Should reject a negative value', () => { + const loadBalancingConfig = { + max_ejection_percent: -1, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_percent parse error: value out of range for percentage/); + }); + }); + describe('success_rate_ejection.enforcement_percentage', () => { + it('Should reject a value above 100', () => { + const loadBalancingConfig = { + success_rate_ejection: { + enforcement_percentage: 101 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /success_rate_ejection\.enforcement_percentage parse error: value out of range for percentage/); + }); + it('Should reject a negative value', () => { + const loadBalancingConfig = { + success_rate_ejection: { + enforcement_percentage: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /success_rate_ejection\.enforcement_percentage parse error: value out of range for percentage/); + }); + }); + describe('failure_percentage_ejection.threshold', () => { + it('Should reject a value above 100', () => { + const loadBalancingConfig = { + failure_percentage_ejection: { + threshold: 101 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /failure_percentage_ejection\.threshold parse error: value out of range for percentage/); + }); + it('Should reject a negative value', () => { + const loadBalancingConfig = { + failure_percentage_ejection: { + threshold: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /failure_percentage_ejection\.threshold parse error: value out of range for percentage/); + }); + }); + describe('failure_percentage_ejection.enforcement_percentage', () => { + it('Should reject a value above 100', () => { + const loadBalancingConfig = { + failure_percentage_ejection: { + enforcement_percentage: 101 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /failure_percentage_ejection\.enforcement_percentage parse error: value out of range for percentage/); + }); + it('Should reject a negative value', () => { + const loadBalancingConfig = { + failure_percentage_ejection: { + enforcement_percentage: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /failure_percentage_ejection\.enforcement_percentage parse error: value out of range for percentage/); + }); + }); + describe('child_policy', () => { + it('Should reject a pick_first child_policy', () => { + const loadBalancingConfig = { + child_policy: [{pick_first: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /outlier_detection LB policy cannot have a pick_first child policy/); + }); + }); +}); + +describe('Outlier detection', () => { + const GOOD_PORTS = 4; + let goodServer: grpc.Server; + let badServer: grpc.Server; + const goodPorts: number[] = []; + let badPort: number; + before(done => { + const eachDone = multiDone(() => { + goodServer.start(); + badServer.start(); + done(); + }, GOOD_PORTS + 1); + goodServer = new grpc.Server(); + goodServer.addService(EchoService.service, goodService); + for (let i = 0; i < GOOD_PORTS; i++) { + goodServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + eachDone(error); + return; + } + goodPorts.push(port); + eachDone(); + }); + } + badServer = new grpc.Server(); + badServer.addService(EchoService.service, badService); + badServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + eachDone(error); + return; + } + badPort = port; + eachDone(); + }); + }); + after(() => { + goodServer.forceShutdown(); + badServer.forceShutdown(); + }); + + function makeManyRequests(makeOneRequest: (callback: (error?: Error) => void) => void, total: number, callback: (error?: Error) => void) { + if (total === 0) { + callback(); + return; + } + makeOneRequest(error => { + if (error) { + callback(error); + return; + } + makeManyRequests(makeOneRequest, total - 1, callback); + }); + } + + it('Should allow normal operation with one server', done => { + const client = new EchoService(`localhost:${goodPorts[0]}`, grpc.credentials.createInsecure(), {'grpc.service_config': defaultOutlierDetectionServiceConfigString}); + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + describe('Success rate', () => { + let makeCheckedRequest: (callback: () => void) => void; + let makeUncheckedRequest:(callback: (error?: Error) => void) => void; + before(() => { + const target = 'ipv4:///' + goodPorts.map(port => `127.0.0.1:${port}`).join(',') + `,127.0.0.1:${badPort}`; + const client = new EchoService(target, grpc.credentials.createInsecure(), {'grpc.service_config': successRateOutlierDetectionServiceConfigString}); + makeUncheckedRequest = (callback: () => void) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + callback(); + } + ); + }; + makeCheckedRequest = (callback: (error?: Error) => void) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + callback(error); + } + ); + }; + }); + it('Should eject a server if it is failing requests', done => { + // Make a large volume of requests + makeManyRequests(makeUncheckedRequest, 50, () => { + // Give outlier detection time to run ejection checks + setTimeout(() => { + // Make enough requests to go around all servers + makeManyRequests(makeCheckedRequest, 10, done); + }, 1000); + }); + }); + it('Should uneject a server after the ejection period', function(done) { + this.timeout(5000); + makeManyRequests(makeUncheckedRequest, 50, () => { + setTimeout(() => { + makeManyRequests(makeCheckedRequest, 10, error => { + if (error) { + done(error); + return; + } + setTimeout(() => { + makeManyRequests(makeCheckedRequest, 10, error => { + assert(error); + done(); + }); + }, 3000); + }); + }, 1000); + }) + }); + }); + describe('Failure percentage', () => { + let makeCheckedRequest: (callback: () => void) => void; + let makeUncheckedRequest:(callback: (error?: Error) => void) => void; + before(() => { + const target = 'ipv4:///' + goodPorts.map(port => `127.0.0.1:${port}`).join(',') + `,127.0.0.1:${badPort}`; + const client = new EchoService(target, grpc.credentials.createInsecure(), {'grpc.service_config': falurePercentageOutlierDetectionServiceConfigString}); + makeUncheckedRequest = (callback: () => void) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + callback(); + } + ); + }; + makeCheckedRequest = (callback: (error?: Error) => void) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + callback(error); + } + ); + }; + }); + it('Should eject a server if it is failing requests', done => { + // Make a large volume of requests + makeManyRequests(makeUncheckedRequest, 50, () => { + // Give outlier detection time to run ejection checks + setTimeout(() => { + // Make enough requests to go around all servers + makeManyRequests(makeCheckedRequest, 10, done); + }, 1000); + }); + }); + it('Should uneject a server after the ejection period', function(done) { + this.timeout(5000); + makeManyRequests(makeUncheckedRequest, 50, () => { + setTimeout(() => { + makeManyRequests(makeCheckedRequest, 10, error => { + if (error) { + done(error); + return; + } + setTimeout(() => { + makeManyRequests(makeCheckedRequest, 10, error => { + assert(error); + done(); + }); + }, 3000); + }); + }, 1000); + }) + }); + }); +}); diff --git a/packages/grpc-js/test/test-prototype-pollution.ts b/packages/grpc-js/test/test-prototype-pollution.ts new file mode 100644 index 000000000..6dc4b293c --- /dev/null +++ b/packages/grpc-js/test/test-prototype-pollution.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2020 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. + * + */ + +import * as assert from 'assert'; + +import { loadPackageDefinition } from '../src'; + +describe('loadPackageDefinition', () => { + it('Should not allow prototype pollution', () => { + loadPackageDefinition({'__proto__.polluted': true} as any); + assert.notStrictEqual(({} as any).polluted, true); + }); + it('Should not allow prototype pollution #2', () => { + loadPackageDefinition({'constructor.prototype.polluted': true} as any); + assert.notStrictEqual(({} as any).polluted, true); + }); +}); diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts new file mode 100644 index 000000000..1d458125b --- /dev/null +++ b/packages/grpc-js/test/test-resolver.ts @@ -0,0 +1,736 @@ +/* + * Copyright 2019 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. + * + */ + +// Allow `any` data type for testing runtime type checking. +// tslint:disable no-any +import * as assert from 'assert'; +import * as resolverManager from '../src/resolver'; +import * as resolver_dns from '../src/resolver-dns'; +import * as resolver_uds from '../src/resolver-uds'; +import * as resolver_ip from '../src/resolver-ip'; +import { ServiceConfig } from '../src/service-config'; +import { StatusObject } from '../src/call-interface'; +import { SubchannelAddress, isTcpSubchannelAddress, subchannelAddressToString } from "../src/subchannel-address"; +import { parseUri, GrpcUri } from '../src/uri-parser'; + +describe('Name Resolver', () => { + before(() => { + resolver_dns.setup(); + resolver_uds.setup(); + resolver_ip.setup(); + }); + describe('DNS Names', function() { + // For some reason DNS queries sometimes take a long time on Windows + this.timeout(4000); + it('Should resolve localhost properly', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('localhost:50051')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 50051 + ) + ); + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 50051 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('Should default to port 443', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('localhost')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 443 + ) + ); + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 443 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('Should correctly represent an ipv4 address', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('1.2.3.4')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '1.2.3.4' && + addr.port === 443 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('Should correctly represent an ipv6 address', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('::1')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 443 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('Should correctly represent a bracketed ipv6 address', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('[::1]:50051')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 50051 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('Should resolve a public address', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('example.com')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert(addressList.length > 0); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + // Created DNS TXT record using TXT sample from https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md + // "grpc_config=[{\"serviceConfig\":{\"loadBalancingPolicy\":\"round_robin\",\"methodConfig\":[{\"name\":[{\"service\":\"MyService\",\"method\":\"Foo\"}],\"waitForReady\":true}]}}]" + it.skip('Should resolve a name with TXT service config', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('grpctest.kleinsch.com')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + if (serviceConfig !== null) { + assert( + serviceConfig.loadBalancingPolicy === 'round_robin', + 'Should have found round robin LB policy' + ); + done(); + } + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it.skip( + 'Should not resolve TXT service config if we disabled service config', + (done) => { + const target = resolverManager.mapUriDefaultScheme( + parseUri('grpctest.kleinsch.com')! + )!; + let count = 0; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert( + serviceConfig === null, + 'Should not have found service config' + ); + count++; + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, { + 'grpc.service_config_disable_resolution': 1, + }); + resolver.updateResolution(); + setTimeout(() => { + assert(count === 1, 'Should have only resolved once'); + done(); + }, 2_000); + } + ); + /* The DNS entry for loopback4.unittest.grpc.io only has a single A record + * with the address 127.0.0.1, but the Mac DNS resolver appears to use + * NAT64 to create an IPv6 address in that case, so it instead returns + * 64:ff9b::7f00:1. Handling that kind of translation is outside of the + * scope of this test, so we are skipping it. The test primarily exists + * as a regression test for https://github.com/grpc/grpc-node/issues/1044, + * and the test 'Should resolve gRPC interop servers' tests the same thing. + */ + it.skip('Should resolve a name with multiple dots', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('loopback4.unittest.grpc.io')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 443 + ), `None of [${addressList.map(addr => subchannelAddressToString(addr))}] matched '127.0.0.1:443'` + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + /* TODO(murgatroid99): re-enable this test, once we can get the IPv6 result + * consistently */ + it.skip('Should resolve a DNS name to an IPv6 address', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('loopback6.unittest.grpc.io')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 443 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + /* This DNS name resolves to only the IPv4 address on Windows, and only the + * IPv6 address on Mac. There is no result that we can consistently test + * for here. */ + it.skip('Should resolve a DNS name to IPv4 and IPv6 addresses', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('loopback46.unittest.grpc.io')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 443 + ), `None of [${addressList.map(addr => subchannelAddressToString(addr))}] matched '127.0.0.1:443'` + ); + /* TODO(murgatroid99): check for IPv6 result, once we can get that + * consistently */ + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('Should resolve a name with a hyphen', done => { + /* TODO(murgatroid99): Find or create a better domain name to test this with. + * This is just the first one I found with a hyphen. */ + const target = resolverManager.mapUriDefaultScheme(parseUri('network-tools.com')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert(addressList.length > 0); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + /* This test also serves as a regression test for + * https://github.com/grpc/grpc-node/issues/1044, specifically handling + * hyphens and multiple periods in a DNS name. It should not be skipped + * unless there is another test for the same issue. */ + it('Should resolve gRPC interop servers', done => { + let completeCount = 0; + const target1 = resolverManager.mapUriDefaultScheme(parseUri('grpc-test.sandbox.googleapis.com')!)!; + const target2 = resolverManager.mapUriDefaultScheme(parseUri('grpc-test4.sandbox.googleapis.com')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert(addressList.length > 0); + completeCount += 1; + if (completeCount === 2) { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + done(); + } + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver1 = resolverManager.createResolver(target1, listener, {}); + resolver1.updateResolution(); + const resolver2 = resolverManager.createResolver(target2, listener, {}); + resolver2.updateResolution(); + }); + it('should not keep repeating successful resolutions', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('localhost')!)!; + let resultCount = 0; + const resolver = resolverManager.createResolver(target, { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 443 + ) + ); + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 443 + ) + ); + resultCount += 1; + if (resultCount === 1) { + process.nextTick(() => resolver.updateResolution()); + } + }, + onError: (error: StatusObject) => { + assert.ifError(error); + }, + }, {'grpc.dns_min_time_between_resolutions_ms': 2000}); + resolver.updateResolution(); + setTimeout(() => { + assert.strictEqual(resultCount, 2, `resultCount ${resultCount} !== 2`); + done(); + }, 10_000); + }).timeout(15_000); + it('should not keep repeating failed resolutions', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('host.invalid')!)!; + let resultCount = 0; + const resolver = resolverManager.createResolver(target, { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert.fail('Resolution succeeded unexpectedly'); + }, + onError: (error: StatusObject) => { + resultCount += 1; + if (resultCount === 1) { + process.nextTick(() => resolver.updateResolution()); + } + }, + }, {}); + resolver.updateResolution(); + setTimeout(() => { + assert.strictEqual(resultCount, 2, `resultCount ${resultCount} !== 2`); + done(); + }, 10_000); + }).timeout(15_000); + }); + describe('UDS Names', () => { + it('Should handle a relative Unix Domain Socket name', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('unix:socket')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => !isTcpSubchannelAddress(addr) && addr.path === 'socket' + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('Should handle an absolute Unix Domain Socket name', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('unix:///tmp/socket')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + !isTcpSubchannelAddress(addr) && addr.path === '/tmp/socket' + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + }); + describe('IP Addresses', () => { + it('should handle one IPv4 address with no port', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('ipv4:127.0.0.1')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 443 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('should handle one IPv4 address with a port', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('ipv4:127.0.0.1:50051')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 50051 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('should handle multiple IPv4 addresses with different ports', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('ipv4:127.0.0.1:50051,127.0.0.1:50052')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 50051 + ) + ); + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 50052 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('should handle one IPv6 address with no port', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('ipv6:::1')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 443 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('should handle one IPv6 address with a port', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('ipv6:[::1]:50051')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 50051 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it('should handle multiple IPv6 addresses with different ports', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('ipv6:[::1]:50051,[::1]:50052')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + // Only handle the first resolution result + listener.onSuccessfulResolution = () => {}; + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 50051 + ) + ); + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 50052 + ) + ); + done(); + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + }); + describe('getDefaultAuthority', () => { + class OtherResolver implements resolverManager.Resolver { + updateResolution() { + return []; + } + + destroy() { + } + + static getDefaultAuthority(target: GrpcUri): string { + return 'other'; + } + } + + it('Should return the correct authority if a different resolver has been registered', () => { + resolverManager.registerResolver('other', OtherResolver); + const target = resolverManager.mapUriDefaultScheme(parseUri('other:name')!)!; + console.log(target); + + const authority = resolverManager.getDefaultAuthority(target); + assert.equal(authority, 'other'); + }); + }); +}); diff --git a/packages/grpc-js/test/test-retry-config.ts b/packages/grpc-js/test/test-retry-config.ts new file mode 100644 index 000000000..e27f236e2 --- /dev/null +++ b/packages/grpc-js/test/test-retry-config.ts @@ -0,0 +1,292 @@ +/* + * Copyright 2022 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. + * + */ + +import assert = require("assert"); +import { validateServiceConfig } from "../src/service-config"; + +function createRetryServiceConfig(retryConfig: object): object { + return { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'A', + method: 'B' + }], + + retryPolicy: retryConfig + } + ] + }; +} + +function createHedgingServiceConfig(hedgingConfig: object): object { + return { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'A', + method: 'B' + }], + + hedgingPolicy: hedgingConfig + } + ] + }; +} + +function createThrottlingServiceConfig(retryThrottling: object): object { + return { + loadBalancingConfig: [], + methodConfig: [], + retryThrottling: retryThrottling + }; +} + +interface TestCase { + description: string; + config: object; + error: RegExp; +} + +const validRetryConfig = { + maxAttempts: 2, + initialBackoff: '1s', + maxBackoff: '1s', + backoffMultiplier: 1, + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] +}; + +const RETRY_TEST_CASES: TestCase[] = [ + { + description: 'omitted maxAttempts', + config: { + initialBackoff: '1s', + maxBackoff: '1s', + backoffMultiplier: 1, + retryableStatusCodes: [14] + }, + error: /retry policy: maxAttempts must be an integer at least 2/ + }, + { + description: 'a low maxAttempts', + config: {...validRetryConfig, maxAttempts: 1}, + error: /retry policy: maxAttempts must be an integer at least 2/ + }, + { + description: 'omitted initialBackoff', + config: { + maxAttempts: 2, + maxBackoff: '1s', + backoffMultiplier: 1, + retryableStatusCodes: [14] + }, + error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'a non-numeric initialBackoff', + config: {...validRetryConfig, initialBackoff: 'abcs'}, + error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'an initialBackoff without an s', + config: {...validRetryConfig, initialBackoff: '123'}, + error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'omitted maxBackoff', + config: { + maxAttempts: 2, + initialBackoff: '1s', + backoffMultiplier: 1, + retryableStatusCodes: [14] + }, + error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'a non-numeric maxBackoff', + config: {...validRetryConfig, maxBackoff: 'abcs'}, + error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'an maxBackoff without an s', + config: {...validRetryConfig, maxBackoff: '123'}, + error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'omitted backoffMultiplier', + config: { + maxAttempts: 2, + initialBackoff: '1s', + maxBackoff: '1s', + retryableStatusCodes: [14] + }, + error: /retry policy: backoffMultiplier must be a number greater than 0/ + }, + { + description: 'a negative backoffMultiplier', + config: {...validRetryConfig, backoffMultiplier: -1}, + error: /retry policy: backoffMultiplier must be a number greater than 0/ + }, + { + description: 'omitted retryableStatusCodes', + config: { + maxAttempts: 2, + initialBackoff: '1s', + maxBackoff: '1s', + backoffMultiplier: 1 + }, + error: /retry policy: retryableStatusCodes is required/ + }, + { + description: 'empty retryableStatusCodes', + config: {...validRetryConfig, retryableStatusCodes: []}, + error: /retry policy: retryableStatusCodes must be non-empty/ + }, + { + description: 'unknown status code name', + config: {...validRetryConfig, retryableStatusCodes: ['abcd']}, + error: /retry policy: retryableStatusCodes value not a status code name/ + }, + { + description: 'out of range status code number', + config: {...validRetryConfig, retryableStatusCodes: [12345]}, + error: /retry policy: retryableStatusCodes value not in status code range/ + } +]; + +const validHedgingConfig = { + maxAttempts: 2 +}; + +const HEDGING_TEST_CASES: TestCase[] = [ + { + description: 'omitted maxAttempts', + config: {}, + error: /hedging policy: maxAttempts must be an integer at least 2/ + }, + { + description: 'a low maxAttempts', + config: {...validHedgingConfig, maxAttempts: 1}, + error: /hedging policy: maxAttempts must be an integer at least 2/ + }, + { + description: 'a non-numeric hedgingDelay', + config: {...validHedgingConfig, hedgingDelay: 'abcs'}, + error: /hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s/ + }, + { + description: 'a hedgingDelay without an s', + config: {...validHedgingConfig, hedgingDelay: '123'}, + error: /hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s/ + }, + { + description: 'unknown status code name', + config: {...validHedgingConfig, nonFatalStatusCodes: ['abcd']}, + error: /hedging policy: nonFatalStatusCodes value not a status code name/ + }, + { + description: 'out of range status code number', + config: {...validHedgingConfig, nonFatalStatusCodes: [12345]}, + error: /hedging policy: nonFatalStatusCodes value not in status code range/ + } +]; + +const validThrottlingConfig = { + maxTokens: 100, + tokenRatio: 0.1 +}; + +const THROTTLING_TEST_CASES: TestCase[] = [ + { + description: 'omitted maxTokens', + config: {tokenRatio: 0.1}, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + }, + { + description: 'a large maxTokens', + config: {...validThrottlingConfig, maxTokens: 1001}, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + }, + { + description: 'zero maxTokens', + config: {...validThrottlingConfig, maxTokens: 0}, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + }, + { + description: 'omitted tokenRatio', + config: {maxTokens: 100}, + error: /retryThrottling: tokenRatio must be a number greater than 0/ + }, + { + description: 'zero tokenRatio', + config: {...validThrottlingConfig, tokenRatio: 0}, + error: /retryThrottling: tokenRatio must be a number greater than 0/ + } +]; + +describe('Retry configs', () => { + describe('Retry', () => { + it('Should accept a valid config', () => { + assert.doesNotThrow(() => { + validateServiceConfig(createRetryServiceConfig(validRetryConfig)); + }); + }); + for (const testCase of RETRY_TEST_CASES) { + it(`Should reject ${testCase.description}`, () => { + assert.throws(() => { + validateServiceConfig(createRetryServiceConfig(testCase.config)); + }, testCase.error); + }); + } + }); + describe('Hedging', () => { + it('Should accept valid configs', () => { + assert.doesNotThrow(() => { + validateServiceConfig(createHedgingServiceConfig(validHedgingConfig)); + }); + assert.doesNotThrow(() => { + validateServiceConfig(createHedgingServiceConfig({...validHedgingConfig, hedgingDelay: '1s'})); + }); + assert.doesNotThrow(() => { + validateServiceConfig(createHedgingServiceConfig({...validHedgingConfig, nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED']})); + }); + }); + for (const testCase of HEDGING_TEST_CASES) { + it(`Should reject ${testCase.description}`, () => { + assert.throws(() => { + validateServiceConfig(createHedgingServiceConfig(testCase.config)); + }, testCase.error); + }); + } + }); + describe('Throttling', () => { + it('Should accept a valid config', () => { + assert.doesNotThrow(() => { + validateServiceConfig(createThrottlingServiceConfig(validThrottlingConfig)); + }); + }); + for (const testCase of THROTTLING_TEST_CASES) { + it(`Should reject ${testCase.description}`, () => { + assert.throws(() => { + validateServiceConfig(createThrottlingServiceConfig(testCase.config)); + }, testCase.error); + }); + } + }); +}); \ No newline at end of file diff --git a/packages/grpc-js/test/test-retry.ts b/packages/grpc-js/test/test-retry.ts new file mode 100644 index 000000000..66c0f7941 --- /dev/null +++ b/packages/grpc-js/test/test-retry.ts @@ -0,0 +1,364 @@ +/* + * Copyright 2022 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. + * + */ + +import * as assert from 'assert'; +import * as path from 'path'; +import * as grpc from '../src'; +import { loadProtoFile } from './common'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const EchoService = loadProtoFile(protoFile) + .EchoService as grpc.ServiceClientConstructor; + +const serviceImpl = { + echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + const succeedOnRetryAttempt = call.metadata.get('succeed-on-retry-attempt'); + const previousAttempts = call.metadata.get('grpc-previous-rpc-attempts'); + if (succeedOnRetryAttempt.length === 0 || (previousAttempts.length > 0 && previousAttempts[0] === succeedOnRetryAttempt[0])) { + callback(null, call.request); + } else { + const statusCode = call.metadata.get('respond-with-status'); + const code = statusCode[0] ? Number.parseInt(statusCode[0] as string) : grpc.status.UNKNOWN; + callback({ + code: code, + details: `Failed on retry ${previousAttempts[0] ?? 0}` + }); + } + } +} + +describe('Retries', () => { + let server: grpc.Server; + let port: number; + before((done) => { + server = new grpc.Server(); + server.addService(EchoService.service, serviceImpl); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, portNumber) => { + if (error) { + done(error); + return; + } + port = portNumber; + server.start(); + done(); + }); + }); + + after(() => { + server.forceShutdown(); + }); + + describe('Client with retries disabled', () => { + let client: InstanceType; + before(() => { + client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.enable_retries': 0}); + }); + + after(() =>{ + client.close(); + }); + + it('Should be able to make a basic request', (done) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should fail if the server fails the first request', (done) =>{ + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '1'); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 0'); + done(); + } + ); + }); + }); + + describe('Client with retries enabled but not configured', () => { + let client: InstanceType; + before(() => { + client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure()); + }); + + after(() =>{ + client.close(); + }); + + it('Should be able to make a basic request', (done) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should fail if the server fails the first request', (done) =>{ + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '1'); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 0'); + done(); + } + ); + }); + }); + + describe('Client with retries configured', () => { + let client: InstanceType; + before(() => { + const serviceConfig = { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'EchoService' + }], + retryPolicy: { + maxAttempts: 3, + initialBackoff: '0.1s', + maxBackoff: '10s', + backoffMultiplier: 1.2, + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + } + } + ] + } + client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + }); + + after(() =>{ + client.close(); + }); + + it('Should be able to make a basic request', (done) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should succeed with few required attempts', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '2'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should fail with many required attempts', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '4'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 2'); + done(); + } + ); + }); + + it('Should fail with a fatal status code', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '2'); + metadata.set('respond-with-status', `${grpc.status.NOT_FOUND}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 0'); + done(); + } + ); + }); + + it('Should not be able to make more than 5 attempts', (done) => { + const serviceConfig = { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'EchoService' + }], + retryPolicy: { + maxAttempts: 10, + initialBackoff: '0.1s', + maxBackoff: '10s', + backoffMultiplier: 1.2, + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + } + } + ] + } + const client2 = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '6'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client2.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 4'); + done(); + } + ); + }) + }); + + describe('Client with hedging configured', () => { + let client: InstanceType; + before(() => { + const serviceConfig = { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'EchoService' + }], + hedgingPolicy: { + maxAttempts: 3, + nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + } + } + ] + } + client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + }); + + after(() =>{ + client.close(); + }); + + it('Should be able to make a basic request', (done) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should succeed with few required attempts', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '2'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should fail with many required attempts', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '4'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert(error.details.startsWith('Failed on retry')); + done(); + } + ); + }); + + it('Should fail with a fatal status code', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '2'); + metadata.set('respond-with-status', `${grpc.status.NOT_FOUND}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert(error.details.startsWith('Failed on retry')); + done(); + } + ); + }); + + it('Should not be able to make more than 5 attempts', (done) => { + const serviceConfig = { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'EchoService' + }], + hedgingPolicy: { + maxAttempts: 10, + nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + } + } + ] + } + const client2 = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '6'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client2.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert(error.details.startsWith('Failed on retry')); + done(); + } + ); + }) + }); +}); \ No newline at end of file diff --git a/packages/grpc-js/test/test-server-credentials.ts b/packages/grpc-js/test/test-server-credentials.ts new file mode 100644 index 000000000..ec1740f70 --- /dev/null +++ b/packages/grpc-js/test/test-server-credentials.ts @@ -0,0 +1,123 @@ +/* + * Copyright 2019 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. + * + */ + +// Allow `any` data type for testing runtime type checking. +// tslint:disable no-any +import * as assert from 'assert'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { ServerCredentials } from '../src'; + +const ca = readFileSync(join(__dirname, 'fixtures', 'ca.pem')); +const key = readFileSync(join(__dirname, 'fixtures', 'server1.key')); +const cert = readFileSync(join(__dirname, 'fixtures', 'server1.pem')); + +describe('Server Credentials', () => { + describe('createInsecure', () => { + it('creates insecure credentials', () => { + const creds = ServerCredentials.createInsecure(); + + assert.strictEqual(creds._isSecure(), false); + assert.strictEqual(creds._getSettings(), null); + }); + }); + + describe('createSsl', () => { + it('accepts a buffer and array as the first two arguments', () => { + const creds = ServerCredentials.createSsl(ca, []); + + assert.strictEqual(creds._isSecure(), true); + assert.strictEqual(creds._getSettings()?.ca, ca); + }); + + it('accepts a boolean as the third argument', () => { + const creds = ServerCredentials.createSsl(ca, [], true); + + assert.strictEqual(creds._isSecure(), true); + const settings = creds._getSettings(); + assert.strictEqual(settings?.ca, ca); + assert.strictEqual(settings?.requestCert, true); + }); + + it('accepts an object with two buffers in the second argument', () => { + const keyCertPairs = [{ private_key: key, cert_chain: cert }]; + const creds = ServerCredentials.createSsl(null, keyCertPairs); + + assert.strictEqual(creds._isSecure(), true); + const settings = creds._getSettings(); + assert.deepStrictEqual(settings?.cert, [cert]); + assert.deepStrictEqual(settings?.key, [key]); + }); + + it('accepts multiple objects in the second argument', () => { + const keyCertPairs = [ + { private_key: key, cert_chain: cert }, + { private_key: key, cert_chain: cert }, + ]; + const creds = ServerCredentials.createSsl(null, keyCertPairs, false); + + assert.strictEqual(creds._isSecure(), true); + const settings = creds._getSettings(); + assert.deepStrictEqual(settings?.cert, [cert, cert]); + assert.deepStrictEqual(settings?.key, [key, key]); + }); + + it('fails if the second argument is not an Array', () => { + assert.throws(() => { + ServerCredentials.createSsl(ca, 'test' as any); + }, /TypeError: keyCertPairs must be an array/); + }); + + it('fails if the first argument is a non-Buffer value', () => { + assert.throws(() => { + ServerCredentials.createSsl('test' as any, []); + }, /TypeError: rootCerts must be null or a Buffer/); + }); + + it('fails if the third argument is a non-boolean value', () => { + assert.throws(() => { + ServerCredentials.createSsl(ca, [], 'test' as any); + }, /TypeError: checkClientCertificate must be a boolean/); + }); + + it('fails if the array elements are not objects', () => { + assert.throws(() => { + ServerCredentials.createSsl(ca, ['test'] as any); + }, /TypeError: keyCertPair\[0\] must be an object/); + + assert.throws(() => { + ServerCredentials.createSsl(ca, [null] as any); + }, /TypeError: keyCertPair\[0\] must be an object/); + }); + + it('fails if the object does not have a Buffer private key', () => { + const keyCertPairs: any = [{ private_key: 'test', cert_chain: cert }]; + + assert.throws(() => { + ServerCredentials.createSsl(null, keyCertPairs); + }, /TypeError: keyCertPair\[0\].private_key must be a Buffer/); + }); + + it('fails if the object does not have a Buffer cert chain', () => { + const keyCertPairs: any = [{ private_key: key, cert_chain: 'test' }]; + + assert.throws(() => { + ServerCredentials.createSsl(null, keyCertPairs); + }, /TypeError: keyCertPair\[0\].cert_chain must be a Buffer/); + }); + }); +}); diff --git a/packages/grpc-js/test/test-server-deadlines.ts b/packages/grpc-js/test/test-server-deadlines.ts new file mode 100644 index 000000000..c1152309d --- /dev/null +++ b/packages/grpc-js/test/test-server-deadlines.ts @@ -0,0 +1,186 @@ +/* + * Copyright 2019 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. + * + */ + +// Allow `any` data type for testing runtime type checking. +// tslint:disable no-any +import * as assert from 'assert'; +import * as path from 'path'; + +import * as grpc from '../src'; +import { Server, ServerCredentials } from '../src'; +import { ServiceError } from '../src/call'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; +import { + sendUnaryData, + ServerUnaryCall, + ServerWritableStream, +} from '../src/server-call'; + +import { loadProtoFile } from './common'; + +const clientInsecureCreds = grpc.credentials.createInsecure(); +const serverInsecureCreds = ServerCredentials.createInsecure(); + +describe('Server deadlines', () => { + let server: Server; + let client: ServiceClient; + + before(done => { + const protoFile = path.join(__dirname, 'fixtures', 'test_service.proto'); + const testServiceDef = loadProtoFile(protoFile); + const testServiceClient = testServiceDef.TestService as ServiceClientConstructor; + + server = new Server(); + server.addService(testServiceClient.service, { + unary(call: ServerUnaryCall, cb: sendUnaryData) { + setTimeout(() => { + cb(null, {}); + }, 2000); + }, + }); + + server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { + assert.ifError(err); + client = new testServiceClient(`localhost:${port}`, clientInsecureCreds); + server.start(); + done(); + }); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('works with deadlines', done => { + const metadata = new grpc.Metadata(); + const { + path, + requestSerialize: serialize, + responseDeserialize: deserialize, + } = client.unary as any; + + metadata.set('grpc-timeout', '100m'); + client.makeUnaryRequest( + path, + serialize, + deserialize, + {}, + metadata, + {}, + (error: any, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + assert.strictEqual(error.details, 'Deadline exceeded'); + done(); + } + ); + }); + + it('rejects invalid deadline', done => { + const metadata = new grpc.Metadata(); + const { + path, + requestSerialize: serialize, + responseDeserialize: deserialize, + } = client.unary as any; + + metadata.set('grpc-timeout', 'Infinity'); + client.makeUnaryRequest( + path, + serialize, + deserialize, + {}, + metadata, + {}, + (error: any, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.OUT_OF_RANGE); + assert.strictEqual(error.details, 'Invalid deadline'); + done(); + } + ); + }); +}); + +describe('Cancellation', () => { + let server: Server; + let client: ServiceClient; + let inHandler = false; + let cancelledInServer = false; + + before(done => { + const protoFile = path.join(__dirname, 'fixtures', 'test_service.proto'); + const testServiceDef = loadProtoFile(protoFile); + const testServiceClient = testServiceDef.TestService as ServiceClientConstructor; + + server = new Server(); + server.addService(testServiceClient.service, { + serverStream(stream: ServerWritableStream) { + inHandler = true; + stream.on('cancelled', () => { + stream.write({}); + stream.end(); + cancelledInServer = true; + }); + }, + }); + + server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { + assert.ifError(err); + client = new testServiceClient(`localhost:${port}`, clientInsecureCreds); + server.start(); + done(); + }); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('handles requests cancelled by the client', done => { + const call = client.serverStream({}); + + call.on('data', assert.ifError); + call.on('error', (error: ServiceError) => { + assert.strictEqual(error.code, grpc.status.CANCELLED); + assert.strictEqual(error.details, 'Cancelled on client'); + waitForServerCancel(); + }); + + function waitForHandler() { + if (inHandler === true) { + call.cancel(); + return; + } + + setImmediate(waitForHandler); + } + + function waitForServerCancel() { + if (cancelledInServer === true) { + done(); + return; + } + + setImmediate(waitForServerCancel); + } + + waitForHandler(); + }); +}); diff --git a/packages/grpc-js/test/test-server-errors.ts b/packages/grpc-js/test/test-server-errors.ts new file mode 100644 index 000000000..2ad67c6cc --- /dev/null +++ b/packages/grpc-js/test/test-server-errors.ts @@ -0,0 +1,789 @@ +/* + * Copyright 2019 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. + * + */ + +// Allow `any` data type for testing runtime type checking. +// tslint:disable no-any +import * as assert from 'assert'; +import { join } from 'path'; + +import * as grpc from '../src'; +import { Server } from '../src'; +import { ServiceError } from '../src/call'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; +import { + sendUnaryData, + ServerDuplexStream, + ServerReadableStream, + ServerUnaryCall, + ServerWritableStream, +} from '../src/server-call'; + +import { loadProtoFile } from './common'; +import { CompressionAlgorithms } from '../src/compression-algorithms'; + +const protoFile = join(__dirname, 'fixtures', 'test_service.proto'); +const testServiceDef = loadProtoFile(protoFile); +const testServiceClient = testServiceDef.TestService as ServiceClientConstructor; +const clientInsecureCreds = grpc.credentials.createInsecure(); +const serverInsecureCreds = grpc.ServerCredentials.createInsecure(); + +describe('Client malformed response handling', () => { + let server: Server; + let client: ServiceClient; + const badArg = Buffer.from([0xff]); + + before(done => { + const malformedTestService = { + unary: { + path: '/TestService/Unary', + requestStream: false, + responseStream: false, + requestDeserialize: identity, + responseSerialize: identity, + }, + clientStream: { + path: '/TestService/ClientStream', + requestStream: true, + responseStream: false, + requestDeserialize: identity, + responseSerialize: identity, + }, + serverStream: { + path: '/TestService/ServerStream', + requestStream: false, + responseStream: true, + requestDeserialize: identity, + responseSerialize: identity, + }, + bidiStream: { + path: '/TestService/BidiStream', + requestStream: true, + responseStream: true, + requestDeserialize: identity, + responseSerialize: identity, + }, + } as any; + + server = new Server(); + + server.addService(malformedTestService, { + unary(call: ServerUnaryCall, cb: sendUnaryData) { + cb(null, badArg); + }, + + clientStream( + stream: ServerReadableStream, + cb: sendUnaryData + ) { + stream.on('data', noop); + stream.on('end', () => { + cb(null, badArg); + }); + }, + + serverStream(stream: ServerWritableStream) { + stream.write(badArg); + stream.end(); + }, + + bidiStream(stream: ServerDuplexStream) { + stream.on('data', () => { + // Ignore requests + stream.write(badArg); + }); + + stream.on('end', () => { + stream.end(); + }); + }, + }); + + server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { + assert.ifError(err); + client = new testServiceClient(`localhost:${port}`, clientInsecureCreds); + server.start(); + done(); + }); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('should get an INTERNAL status with a unary call', done => { + client.unary({}, (err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + }); + + it('should get an INTERNAL status with a client stream call', done => { + const call = client.clientStream((err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + + call.write({}); + call.end(); + }); + + it('should get an INTERNAL status with a server stream call', done => { + const call = client.serverStream({}); + + call.on('data', noop); + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + }); + + it('should get an INTERNAL status with a bidi stream call', done => { + const call = client.bidiStream(); + + call.on('data', noop); + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + + call.write({}); + call.end(); + }); +}); + +describe('Server serialization failure handling', () => { + let client: ServiceClient; + let server: Server; + + before(done => { + function serializeFail(obj: any) { + throw new Error('Serialization failed'); + } + + const malformedTestService = { + unary: { + path: '/TestService/Unary', + requestStream: false, + responseStream: false, + requestDeserialize: identity, + responseSerialize: serializeFail, + }, + clientStream: { + path: '/TestService/ClientStream', + requestStream: true, + responseStream: false, + requestDeserialize: identity, + responseSerialize: serializeFail, + }, + serverStream: { + path: '/TestService/ServerStream', + requestStream: false, + responseStream: true, + requestDeserialize: identity, + responseSerialize: serializeFail, + }, + bidiStream: { + path: '/TestService/BidiStream', + requestStream: true, + responseStream: true, + requestDeserialize: identity, + responseSerialize: serializeFail, + }, + }; + + server = new Server(); + server.addService(malformedTestService as any, { + unary(call: ServerUnaryCall, cb: sendUnaryData) { + cb(null, {}); + }, + + clientStream( + stream: ServerReadableStream, + cb: sendUnaryData + ) { + stream.on('data', noop); + stream.on('end', () => { + cb(null, {}); + }); + }, + + serverStream(stream: ServerWritableStream) { + stream.write({}); + stream.end(); + }, + + bidiStream(stream: ServerDuplexStream) { + stream.on('data', () => { + // Ignore requests + stream.write({}); + }); + stream.on('end', () => { + stream.end(); + }); + }, + }); + + server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { + assert.ifError(err); + client = new testServiceClient(`localhost:${port}`, clientInsecureCreds); + server.start(); + done(); + }); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('should get an INTERNAL status with a unary call', done => { + client.unary({}, (err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + }); + + it('should get an INTERNAL status with a client stream call', done => { + const call = client.clientStream((err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + + call.write({}); + call.end(); + }); + + it('should get an INTERNAL status with a server stream call', done => { + const call = client.serverStream({}); + + call.on('data', noop); + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + }); +}); + +describe('Other conditions', () => { + let client: ServiceClient; + let server: Server; + let port: number; + + before(done => { + const trailerMetadata = new grpc.Metadata(); + + server = new Server(); + trailerMetadata.add('trailer-present', 'yes'); + + server.addService(testServiceClient.service, { + unary(call: ServerUnaryCall, cb: sendUnaryData) { + const req = call.request; + + if (req.error) { + const details = req.message || 'Requested error'; + + cb( + { code: grpc.status.UNKNOWN, details } as ServiceError, + null, + trailerMetadata + ); + } else { + cb(null, { count: 1, message: 'a'.repeat(req.responseLength) }, trailerMetadata); + } + }, + + clientStream( + stream: ServerReadableStream, + cb: sendUnaryData + ) { + let count = 0; + let errored = false; + let responseLength = 0; + + stream.on('data', (data: any) => { + if (data.error) { + const message = data.message || 'Requested error'; + errored = true; + cb(new Error(message) as ServiceError, null, trailerMetadata); + } else { + responseLength += data.responseLength; + count++; + } + }); + + stream.on('end', () => { + if (!errored) { + cb(null, { count, message: 'a'.repeat(responseLength) }, trailerMetadata); + } + }); + }, + + serverStream(stream: ServerWritableStream) { + const req = stream.request; + + if (req.error) { + stream.emit('error', { + code: grpc.status.UNKNOWN, + details: req.message || 'Requested error', + metadata: trailerMetadata, + }); + } else { + for (let i = 1; i <= 5; i++) { + stream.write({ count: i, message: 'a'.repeat(req.responseLength) }); + if (req.errorAfter && req.errorAfter === i) { + stream.emit('error', { + code: grpc.status.UNKNOWN, + details: req.message || 'Requested error', + metadata: trailerMetadata, + }); + break; + } + } + if (!req.errorAfter) { + stream.end(trailerMetadata); + } + } + }, + + bidiStream(stream: ServerDuplexStream) { + let count = 0; + stream.on('data', (data: any) => { + if (data.error) { + const message = data.message || 'Requested error'; + const err = new Error(message) as ServiceError; + + err.metadata = trailerMetadata.clone(); + err.metadata.add('count', '' + count); + stream.emit('error', err); + } else { + stream.write({ count, message: 'a'.repeat(data.responseLength) }); + count++; + } + }); + + stream.on('end', () => { + stream.end(trailerMetadata); + }); + }, + }); + + server.bindAsync('localhost:0', serverInsecureCreds, (err, _port) => { + assert.ifError(err); + port = _port; + client = new testServiceClient(`localhost:${port}`, clientInsecureCreds); + server.start(); + done(); + }); + }); + + after(() => { + client.close(); + server.forceShutdown(); + }); + + describe('Server receiving bad input', () => { + let misbehavingClient: ServiceClient; + const badArg = Buffer.from([0xff]); + + before(() => { + const testServiceAttrs = { + unary: { + path: '/TestService/Unary', + requestStream: false, + responseStream: false, + requestSerialize: identity, + responseDeserialize: identity, + }, + clientStream: { + path: '/TestService/ClientStream', + requestStream: true, + responseStream: false, + requestSerialize: identity, + responseDeserialize: identity, + }, + serverStream: { + path: '/TestService/ServerStream', + requestStream: false, + responseStream: true, + requestSerialize: identity, + responseDeserialize: identity, + }, + bidiStream: { + path: '/TestService/BidiStream', + requestStream: true, + responseStream: true, + requestSerialize: identity, + responseDeserialize: identity, + }, + } as any; + + const client = grpc.makeGenericClientConstructor( + testServiceAttrs, + 'TestService' + ); + + misbehavingClient = new client(`localhost:${port}`, clientInsecureCreds); + }); + + after(() => { + misbehavingClient.close(); + }); + + it('should respond correctly to a unary call', done => { + misbehavingClient.unary(badArg, (err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + }); + + it('should respond correctly to a client stream', done => { + const call = misbehavingClient.clientStream( + (err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + } + ); + + call.write(badArg); + call.end(); + }); + + it('should respond correctly to a server stream', done => { + const call = misbehavingClient.serverStream(badArg); + + call.on('data', (data: any) => { + assert.fail(data); + }); + + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + }); + + it('should respond correctly to a bidi stream', done => { + const call = misbehavingClient.bidiStream(); + + call.on('data', (data: any) => { + assert.fail(data); + }); + + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.INTERNAL); + done(); + }); + + call.write(badArg); + call.end(); + }); + }); + + describe('Trailing metadata', () => { + it('should be present when a unary call succeeds', done => { + let count = 0; + const call = client.unary( + { error: false }, + (err: ServiceError, data: any) => { + assert.ifError(err); + + count++; + if (count === 2) { + done(); + } + } + ); + + call.on('status', (status: grpc.StatusObject) => { + assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']); + + count++; + if (count === 2) { + done(); + } + }); + }); + + it('should be present when a unary call fails', done => { + let count = 0; + const call = client.unary( + { error: true }, + (err: ServiceError, data: any) => { + assert(err); + + count++; + if (count === 2) { + done(); + } + } + ); + + call.on('status', (status: grpc.StatusObject) => { + assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']); + + count++; + if (count === 2) { + done(); + } + }); + }); + + it('should be present when a client stream call succeeds', done => { + let count = 0; + const call = client.clientStream((err: ServiceError, data: any) => { + assert.ifError(err); + + count++; + if (count === 2) { + done(); + } + }); + + call.write({ error: false }); + call.write({ error: false }); + call.end(); + + call.on('status', (status: grpc.StatusObject) => { + assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']); + + count++; + if (count === 2) { + done(); + } + }); + }); + + it('should be present when a client stream call fails', done => { + let count = 0; + const call = client.clientStream((err: ServiceError, data: any) => { + assert(err); + + count++; + if (count === 2) { + done(); + } + }); + + call.write({ error: false }); + call.write({ error: true }); + call.end(); + + call.on('status', (status: grpc.StatusObject) => { + assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']); + + count++; + if (count === 2) { + done(); + } + }); + }); + + it('should be present when a server stream call succeeds', done => { + const call = client.serverStream({ error: false }); + + call.on('data', noop); + call.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.OK); + assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + + it('should be present when a server stream call fails', done => { + const call = client.serverStream({ error: true }); + + call.on('data', noop); + call.on('error', (error: ServiceError) => { + assert.deepStrictEqual(error.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + + it('should be present when a bidi stream succeeds', done => { + const call = client.bidiStream(); + + call.write({ error: false }); + call.write({ error: false }); + call.end(); + call.on('data', noop); + call.on('status', (status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.OK); + assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + + it('should be present when a bidi stream fails', done => { + const call = client.bidiStream(); + + call.write({ error: false }); + call.write({ error: true }); + call.end(); + call.on('data', noop); + call.on('error', (error: ServiceError) => { + assert.deepStrictEqual(error.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + }); + + describe('Error object should contain the status', () => { + it('for a unary call', done => { + client.unary({ error: true }, (err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNKNOWN); + assert.strictEqual(err.details, 'Requested error'); + done(); + }); + }); + + it('for a client stream call', done => { + const call = client.clientStream((err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNKNOWN); + assert.strictEqual(err.details, 'Requested error'); + done(); + }); + + call.write({ error: false }); + call.write({ error: true }); + call.end(); + }); + + it('for a server stream call', done => { + const call = client.serverStream({ error: true }); + + call.on('data', noop); + call.on('error', (error: ServiceError) => { + assert.strictEqual(error.code, grpc.status.UNKNOWN); + assert.strictEqual(error.details, 'Requested error'); + done(); + }); + }); + + it('for a bidi stream call', done => { + const call = client.bidiStream(); + + call.write({ error: false }); + call.write({ error: true }); + call.end(); + call.on('data', noop); + call.on('error', (error: ServiceError) => { + assert.strictEqual(error.code, grpc.status.UNKNOWN); + assert.strictEqual(error.details, 'Requested error'); + done(); + }); + }); + + it('for a UTF-8 error message', done => { + client.unary( + { error: true, message: '測試字符串' }, + (err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNKNOWN); + assert.strictEqual(err.details, '測試字符串'); + done(); + } + ); + }); + + it('for an error message with a comma', done => { + client.unary( + { error: true, message: 'an error message, with a comma' }, + (err: ServiceError, data: any) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNKNOWN); + assert.strictEqual(err.details, 'an error message, with a comma'); + done(); + } + ); + }); + }); + + describe('should handle server stream errors correctly', () => { + it('should emit data for all messages before error', (done) => { + const expectedDataCount = 2; + const call = client.serverStream({ errorAfter: expectedDataCount }); + + let actualDataCount = 0; + call.on('data', () => { + ++actualDataCount; + }); + call.on('error', (error: ServiceError) => { + assert.strictEqual(error.code, grpc.status.UNKNOWN); + assert.strictEqual(error.details, 'Requested error'); + assert.strictEqual(actualDataCount, expectedDataCount); + done(); + }); + }); + }); + + describe('Max message size', () => { + const largeMessage = 'a'.repeat(10_000_000); + it('Should be enforced on the server', done => { + client.unary({ message: largeMessage }, (error?: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + }); + it('Should be enforced on the client', done => { + client.unary({ responseLength: 10_000_000 }, (error?: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + }); + describe('Compressed messages', () => { + it('Should be enforced with gzip', done => { + const compressingClient = new testServiceClient(`localhost:${port}`, clientInsecureCreds, {'grpc.default_compression_algorithm': CompressionAlgorithms.gzip}); + compressingClient.unary({ message: largeMessage }, (error?: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + assert.match(error.details, /Received message that decompresses to a size larger/); + done(); + }); + }); + it('Should be enforced with deflate', done => { + const compressingClient = new testServiceClient(`localhost:${port}`, clientInsecureCreds, {'grpc.default_compression_algorithm': CompressionAlgorithms.deflate}); + compressingClient.unary({ message: largeMessage }, (error?: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + assert.match(error.details, /Received message that decompresses to a size larger/); + done(); + }); + }); + }); + }); +}); + +function identity(arg: any): any { + return arg; +} + +function noop(): void {} diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts new file mode 100644 index 000000000..68890df55 --- /dev/null +++ b/packages/grpc-js/test/test-server.ts @@ -0,0 +1,1032 @@ +/* + * Copyright 2019 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. + * + */ + +// Allow `any` data type for testing runtime type checking. +// tslint:disable no-any +import * as assert from 'assert'; +import * as fs from 'fs'; +import * as http2 from 'http2'; +import * as path from 'path'; +import * as protoLoader from '@grpc/proto-loader'; + +import * as grpc from '../src'; +import { Server, ServerCredentials } from '../src'; +import { ServiceError } from '../src/call'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; +import { sendUnaryData, ServerUnaryCall, ServerDuplexStream } from '../src/server-call'; + +import { assert2, loadProtoFile } from './common'; +import { TestServiceClient, TestServiceHandlers } from './generated/TestService'; +import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; +import { Request__Output } from './generated/Request'; +import { CompressionAlgorithms } from '../src/compression-algorithms'; + +const loadedTestServiceProto = protoLoader.loadSync(path.join(__dirname, 'fixtures/test_service.proto'), { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true +}); + +const testServiceGrpcObject = grpc.loadPackageDefinition(loadedTestServiceProto) as unknown as TestServiceGrpcType; + +const ca = fs.readFileSync(path.join(__dirname, 'fixtures', 'ca.pem')); +const key = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.key')); +const cert = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.pem')); +function noop(): void {} + +describe('Server', () => { + describe('constructor', () => { + it('should work with no arguments', () => { + assert.doesNotThrow(() => { + new Server(); // tslint:disable-line:no-unused-expression + }); + }); + + it('should work with an empty object argument', () => { + assert.doesNotThrow(() => { + new Server({}); // tslint:disable-line:no-unused-expression + }); + }); + + it('should be an instance of Server', () => { + const server = new Server(); + + assert(server instanceof Server); + }); + }); + + describe('bindAsync', () => { + it('binds with insecure credentials', done => { + const server = new Server(); + + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + assert(typeof port === 'number' && port > 0); + server.tryShutdown(done); + } + ); + }); + + it('binds with secure credentials', done => { + const server = new Server(); + const creds = ServerCredentials.createSsl( + ca, + [{ private_key: key, cert_chain: cert }], + true + ); + + server.bindAsync('localhost:0', creds, (err, port) => { + assert.ifError(err); + assert(typeof port === 'number' && port > 0); + server.tryShutdown(done); + }); + }); + + it('throws if bind is called after the server is started', done => { + const server = new Server(); + + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + server.start(); + assert.throws(() => { + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + noop + ); + }, /server is already started/); + server.tryShutdown(done); + } + ); + }); + + it('throws on invalid inputs', () => { + const server = new Server(); + + assert.throws(() => { + server.bindAsync(null as any, ServerCredentials.createInsecure(), noop); + }, /port must be a string/); + + assert.throws(() => { + server.bindAsync('localhost:0', null as any, noop); + }, /creds must be a ServerCredentials object/); + + assert.throws(() => { + server.bindAsync('localhost:0', grpc.credentials.createInsecure() as any, noop); + }, /creds must be a ServerCredentials object/); + + assert.throws(() => { + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + null as any + ); + }, /callback must be a function/); + }); + }); + + describe('start', () => { + let server: Server; + + beforeEach(done => { + server = new Server(); + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), done); + }); + + afterEach(done => { + server.tryShutdown(done); + }); + + it('starts without error', () => { + assert.doesNotThrow(() => { + server.start(); + }); + }); + + it('throws if started twice', () => { + server.start(); + assert.throws(() => { + server.start(); + }, /server is already started/); + }); + + it('throws if the server is not bound', () => { + const server = new Server(); + + assert.throws(() => { + server.start(); + }, /server must be bound in order to start/); + }); + }); + + describe('addService', () => { + const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto'); + const mathClient = (loadProtoFile(mathProtoFile).math as any).Math; + const mathServiceAttrs = mathClient.service; + const dummyImpls = { div() {}, divMany() {}, fib() {}, sum() {} }; + const altDummyImpls = { Div() {}, DivMany() {}, Fib() {}, Sum() {} }; + + it('succeeds with a single service', () => { + const server = new Server(); + + assert.doesNotThrow(() => { + server.addService(mathServiceAttrs, dummyImpls); + }); + }); + + it('fails to add an empty service', () => { + const server = new Server(); + + assert.throws(() => { + server.addService({}, dummyImpls); + }, /Cannot add an empty service to a server/); + }); + + it('fails with conflicting method names', () => { + const server = new Server(); + + server.addService(mathServiceAttrs, dummyImpls); + assert.throws(() => { + server.addService(mathServiceAttrs, dummyImpls); + }, /Method handler for .+ already provided/); + }); + + it('supports method names as originally written', () => { + const server = new Server(); + + assert.doesNotThrow(() => { + server.addService(mathServiceAttrs, altDummyImpls); + }); + }); + + it('succeeds after server has been started', done => { + const server = new Server(); + + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + server.start(); + assert.doesNotThrow(() => { + server.addService(mathServiceAttrs, dummyImpls); + }); + server.tryShutdown(done); + } + ); + }); + }); + + describe('removeService', () => { + let server: Server; + let client: ServiceClient; + + const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto'); + const mathClient = (loadProtoFile(mathProtoFile).math as any).Math; + const mathServiceAttrs = mathClient.service; + const dummyImpls = { div() {}, divMany() {}, fib() {}, sum() {} }; + + beforeEach(done => { + server = new Server(); + server.addService(mathServiceAttrs, dummyImpls); + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + client = new mathClient( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + server.start(); + done(); + } + ); + }); + + afterEach(done => { + client.close(); + server.tryShutdown(done); + }); + + it('succeeds with a single service by removing all method handlers', done => { + server.removeService(mathServiceAttrs); + + let methodsVerifiedCount = 0; + const methodsToVerify = Object.keys(mathServiceAttrs); + + const assertFailsWithUnimplementedError = (error: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + methodsVerifiedCount++; + if (methodsVerifiedCount === methodsToVerify.length) { + done(); + } + }; + + methodsToVerify.forEach((method) => { + const call = client[method]({}, assertFailsWithUnimplementedError); // for unary + call.on('error', assertFailsWithUnimplementedError); // for streamed + }); + }); + + it('fails for non-object service definition argument', () => { + assert.throws(() => { + server.removeService('upsie' as any) + }, /removeService.*requires object as argument/ + ); + }); + }); + + describe('unregister', () => { + + let server: Server; + let client: ServiceClient; + + const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto'); + const mathClient = (loadProtoFile(mathProtoFile).math as any).Math; + const mathServiceAttrs = mathClient.service; + + beforeEach(done => { + server = new Server(); + server.addService(mathServiceAttrs, { + div(call: ServerUnaryCall, callback: sendUnaryData) { + callback(null, {quotient: '42'}); + }, + }); + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + client = new mathClient( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + server.start(); + done(); + } + ); + }); + + afterEach(done => { + client.close(); + server.tryShutdown(done); + }); + + it('removes handler by name and returns true', done => { + const name = mathServiceAttrs['Div'].path; + assert.strictEqual(server.unregister(name), true, 'Server#unregister should return true on success'); + + client.div( + { divisor: 4, dividend: 3 }, + (error: ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + done(); + } + ); + }); + + it('returns false for unknown handler', () => { + assert.strictEqual(server.unregister('noOneHere'), false, 'Server#unregister should return false on failure'); + }); + }); + + it('throws when unimplemented methods are called', () => { + const server = new Server(); + + assert.throws(() => { + server.addProtoService(); + }, /Not implemented. Use addService\(\) instead/); + + assert.throws(() => { + server.addHttp2Port(); + }, /Not yet implemented/); + + assert.throws(() => { + server.bind('localhost:0', ServerCredentials.createInsecure()); + }, /Not implemented. Use bindAsync\(\) instead/); + }); + + describe('Default handlers', () => { + let server: Server; + let client: ServiceClient; + + const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto'); + const mathClient = (loadProtoFile(mathProtoFile).math as any).Math; + const mathServiceAttrs = mathClient.service; + + before(done => { + server = new Server(); + server.addService(mathServiceAttrs, {}); + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + client = new mathClient( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + server.start(); + done(); + } + ); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('should respond to a unary call with UNIMPLEMENTED', done => { + client.div( + { divisor: 4, dividend: 3 }, + (error: ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + assert.match(error.details, /does not implement the method.*Div/); + done(); + } + ); + }); + + it('should respond to a client stream with UNIMPLEMENTED', done => { + const call = client.sum((error: ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + assert.match(error.details, /does not implement the method.*Sum/); + done(); + }); + + call.end(); + }); + + it('should respond to a server stream with UNIMPLEMENTED', done => { + const call = client.fib({ limit: 5 }); + + call.on('data', (value: any) => { + assert.fail('No messages expected'); + }); + + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); + assert.match(err.details, /does not implement the method.*Fib/); + done(); + }); + }); + + it('should respond to a bidi call with UNIMPLEMENTED', done => { + const call = client.divMany(); + + call.on('data', (value: any) => { + assert.fail('No messages expected'); + }); + + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); + assert.match(err.details, /does not implement the method.*DivMany/); + done(); + }); + + call.end(); + }); + }); + + describe('Unregistered service', () => { + let server: Server; + let client: ServiceClient; + + const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto'); + const mathClient = (loadProtoFile(mathProtoFile).math as any).Math; + + before(done => { + server = new Server(); + // Don't register a service at all + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + client = new mathClient( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + server.start(); + done(); + } + ); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('should respond to a unary call with UNIMPLEMENTED', done => { + client.div( + { divisor: 4, dividend: 3 }, + (error: ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + assert.match(error.details, /does not implement the method.*Div/); + done(); + } + ); + }); + + it('should respond to a client stream with UNIMPLEMENTED', done => { + const call = client.sum((error: ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + assert.match(error.details, /does not implement the method.*Sum/); + done(); + }); + + call.end(); + }); + + it('should respond to a server stream with UNIMPLEMENTED', done => { + const call = client.fib({ limit: 5 }); + + call.on('data', (value: any) => { + assert.fail('No messages expected'); + }); + + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); + assert.match(err.details, /does not implement the method.*Fib/); + done(); + }); + }); + + it('should respond to a bidi call with UNIMPLEMENTED', done => { + const call = client.divMany(); + + call.on('data', (value: any) => { + assert.fail('No messages expected'); + }); + + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); + assert.match(err.details, /does not implement the method.*DivMany/); + done(); + }); + + call.end(); + }); + }); +}); + +describe('Echo service', () => { + let server: Server; + let client: ServiceClient; + const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); + const echoService = loadProtoFile(protoFile) + .EchoService as ServiceClientConstructor; + + const serviceImplementation = { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + callback(null, call.request); + }, + echoBidiStream(call: ServerDuplexStream) { + call.on('data', data => { + call.write(data); + }); + call.on('end', () => { + call.end(); + }); + } + }; + + before(done => { + + server = new Server(); + server.addService(echoService.service, serviceImplementation); + + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + client = new echoService( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + server.start(); + done(); + } + ); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('should echo the recieved message directly', done => { + client.echo( + { value: 'test value', value2: 3 }, + (error: ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + /* This test passes on Node 18 but fails on Node 16. The failure appears to + * be caused by https://github.com/nodejs/node/issues/42713 */ + it.skip('should continue a stream after server shutdown', done => { + const server2 = new Server(); + server2.addService(echoService.service, serviceImplementation); + server2.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { + if (err) { + done(err); + return; + } + const client2 = new echoService(`localhost:${port}`, grpc.credentials.createInsecure()); + server2.start(); + const stream = client2.echoBidiStream(); + const totalMessages = 5; + let messagesSent = 0; + stream.write({ value: 'test value', value2: messagesSent}); + messagesSent += 1; + stream.on('data', () => { + if (messagesSent === 1) { + server2.tryShutdown(assert2.mustCall(() => {})); + } + if (messagesSent >= totalMessages) { + stream.end(); + } else { + stream.write({ value: 'test value', value2: messagesSent}); + messagesSent += 1; + } + }); + stream.on('status', assert2.mustCall((status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.OK); + assert.strictEqual(messagesSent, totalMessages); + })); + stream.on('error', () => {}); + assert2.afterMustCallsSatisfied(done); + }); + }); +}); + +describe('Generic client and server', () => { + function toString(val: any) { + return val.toString(); + } + + function toBuffer(str: string) { + return Buffer.from(str); + } + + function capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + const stringServiceAttrs = { + capitalize: { + path: '/string/capitalize', + requestStream: false, + responseStream: false, + requestSerialize: toBuffer, + requestDeserialize: toString, + responseSerialize: toBuffer, + responseDeserialize: toString, + }, + }; + + describe('String client and server', () => { + let client: ServiceClient; + let server: Server; + + before(done => { + server = new Server(); + + server.addService(stringServiceAttrs as any, { + capitalize( + call: ServerUnaryCall, + callback: sendUnaryData + ) { + callback(null, capitalize(call.request)); + }, + }); + + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + server.start(); + const clientConstr = grpc.makeGenericClientConstructor( + stringServiceAttrs as any, + 'unused_but_lets_appease_typescript_anyway' + ); + client = new clientConstr( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + done(); + } + ); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('Should respond with a capitalized string', done => { + client.capitalize('abc', (err: ServiceError, response: string) => { + assert.ifError(err); + assert.strictEqual(response, 'Abc'); + done(); + }); + }); + }); + + it('responds with HTTP status of 415 on invalid content-type', done => { + const server = new Server(); + const creds = ServerCredentials.createInsecure(); + + server.bindAsync('localhost:0', creds, (err, port) => { + assert.ifError(err); + const client = http2.connect(`http://localhost:${port}`); + let count = 0; + + function makeRequest(headers: http2.IncomingHttpHeaders) { + const req = client.request(headers); + let statusCode: string; + + req.on('response', headers => { + statusCode = headers[http2.constants.HTTP2_HEADER_STATUS] as string; + assert.strictEqual( + statusCode, + http2.constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE + ); + }); + + req.on('end', () => { + assert(statusCode); + count++; + if (count === 2) { + client.close(); + server.tryShutdown(done); + } + }); + + req.end(); + } + + server.start(); + + // Missing Content-Type header. + makeRequest({ ':path': '/' }); + // Invalid Content-Type header. + makeRequest({ ':path': '/', 'content-type': 'application/not-grpc' }); + }); + }); +}); + +describe('Compressed requests', () => { + const testServiceHandlers: TestServiceHandlers = { + Unary(call, callback) { + callback(null, { count: 500000, message: call.request.message }); + }, + + ClientStream(call, callback) { + let timesCalled = 0; + + call.on('data', () => { + timesCalled += 1; + }); + + call.on('end', () => { + callback(null, { count: timesCalled }); + }); + }, + + ServerStream(call) { + const { request } = call; + + for (let i = 0; i < 5; i++) { + call.write({ count: request.message.length }); + } + + call.end(); + }, + + BidiStream(call) { + call.on('data', (data: Request__Output) => { + call.write({ count: data.message.length }); + }); + + call.on('end', () => { + call.end(); + }); + } + }; + + describe('Test service client and server with deflate', () => { + let client: TestServiceClient; + let server: Server; + let assignedPort: number; + + before(done => { + server = new Server(); + server.addService( + testServiceGrpcObject.TestService.service, + testServiceHandlers + ); + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + server.start(); + assignedPort = port; + client = new testServiceGrpcObject.TestService( + `localhost:${assignedPort}`, + grpc.credentials.createInsecure(), + { + 'grpc.default_compression_algorithm': CompressionAlgorithms.deflate + } + ); + done(); + } + ); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('Should compress and decompress when performing unary call', done => { + client.unary({ message: 'foo' }, (err, response) => { + assert.ifError(err); + done(); + }); + }); + + it('Should compress and decompress when performing client stream', done => { + const clientStream = client.clientStream((err, res) => { + assert.ifError(err); + assert.equal(res?.count, 3); + done(); + }); + + clientStream.write({ message: 'foo' }, () => { + clientStream.write({ message: 'bar' }, () => { + clientStream.write({ message: 'baz' }, () => { + setTimeout(() => clientStream.end(), 10); + }); + }); + }); + }); + + it('Should compress and decompress when performing server stream', done => { + const serverStream = client.serverStream({ message: 'foobar' }); + let timesResponded = 0; + + serverStream.on('data', () => { + timesResponded += 1; + }); + + serverStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + serverStream.on('end', () => { + assert.equal(timesResponded, 5); + done(); + }); + }); + + it('Should compress and decompress when performing bidi stream', done => { + const bidiStream = client.bidiStream(); + let timesRequested = 0; + let timesResponded = 0; + + bidiStream.on('data', () => { + timesResponded += 1; + }); + + bidiStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + bidiStream.on('end', () => { + assert.equal(timesResponded, timesRequested); + done(); + }); + + bidiStream.write({ message: 'foo' }, () => { + timesRequested += 1; + bidiStream.write({ message: 'bar' }, () => { + timesRequested += 1; + bidiStream.write({ message: 'baz' }, () => { + timesRequested += 1; + setTimeout(() => bidiStream.end(), 10); + }) + }) + }); + }); + + it('Should compress and decompress with gzip', done => { + client = new testServiceGrpcObject.TestService( + `localhost:${assignedPort}`, + grpc.credentials.createInsecure(), + { + 'grpc.default_compression_algorithm': CompressionAlgorithms.gzip + } + ); + + client.unary({ message: 'foo' }, (err, response) => { + assert.ifError(err); + done(); + }); + }); + + it('Should compress and decompress when performing client stream', done => { + const clientStream = client.clientStream((err, res) => { + assert.ifError(err); + assert.equal(res?.count, 3); + done(); + }); + + clientStream.write({ message: 'foo' }, () => { + clientStream.write({ message: 'bar' }, () => { + clientStream.write({ message: 'baz' }, () => { + setTimeout(() => clientStream.end(), 10); + }); + }); + }); + }); + + it('Should compress and decompress when performing server stream', done => { + const serverStream = client.serverStream({ message: 'foobar' }); + let timesResponded = 0; + + serverStream.on('data', () => { + timesResponded += 1; + }); + + serverStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + serverStream.on('end', () => { + assert.equal(timesResponded, 5); + done(); + }); + }); + + it('Should compress and decompress when performing bidi stream', done => { + const bidiStream = client.bidiStream(); + let timesRequested = 0; + let timesResponded = 0; + + bidiStream.on('data', () => { + timesResponded += 1; + }); + + bidiStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + bidiStream.on('end', () => { + assert.equal(timesResponded, timesRequested); + done(); + }); + + bidiStream.write({ message: 'foo' }, () => { + timesRequested += 1; + bidiStream.write({ message: 'bar' }, () => { + timesRequested += 1; + bidiStream.write({ message: 'baz' }, () => { + timesRequested += 1; + setTimeout(() => bidiStream.end(), 10); + }) + }) + }); + }); + + it('Should handle large messages', done => { + let longMessage = ''; + for (let i = 0; i < 400000; i++) { + const letter = 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)]; + longMessage = longMessage + letter.repeat(10); + } + + client.unary({message: longMessage}, (err, response) => { + assert.ifError(err); + assert.strictEqual(response?.message, longMessage); + done(); + }) + }) + + /* As of Node 16, Writable and Duplex streams validate the encoding + * argument to write, and the flags values we are passing there are not + * valid. We don't currently have an alternative way to pass that flag + * down, so for now this feature is not supported. */ + it.skip('Should not compress requests when the NoCompress write flag is used', done => { + const bidiStream = client.bidiStream(); + let timesRequested = 0; + let timesResponded = 0; + + bidiStream.on('data', () => { + timesResponded += 1; + }); + + bidiStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + bidiStream.on('end', () => { + assert.equal(timesResponded, timesRequested); + done(); + }); + + bidiStream.write({ message: 'foo' }, '2', (err: any) => { + assert.ifError(err); + timesRequested += 1; + setTimeout(() => bidiStream.end(), 10); + }); + }); + }); +}); diff --git a/packages/grpc-js/test/test-status-builder.ts b/packages/grpc-js/test/test-status-builder.ts new file mode 100644 index 000000000..7bf12203b --- /dev/null +++ b/packages/grpc-js/test/test-status-builder.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2019 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. + * + */ + +import * as assert from 'assert'; + +import * as grpc from '../src'; +import { StatusBuilder } from '../src/status-builder'; + +describe('StatusBuilder', () => { + it('is exported by the module', () => { + assert.strictEqual(StatusBuilder, grpc.StatusBuilder); + }); + + it('builds a status object', () => { + const builder = new StatusBuilder(); + const metadata = new grpc.Metadata(); + let result; + + assert.deepStrictEqual(builder.build(), {}); + result = builder.withCode(grpc.status.OK); + assert.strictEqual(result, builder); + assert.deepStrictEqual(builder.build(), { code: grpc.status.OK }); + result = builder.withDetails('foobar'); + assert.strictEqual(result, builder); + assert.deepStrictEqual(builder.build(), { + code: grpc.status.OK, + details: 'foobar', + }); + result = builder.withMetadata(metadata); + assert.strictEqual(result, builder); + assert.deepStrictEqual(builder.build(), { + code: grpc.status.OK, + details: 'foobar', + metadata, + }); + }); +}); diff --git a/packages/grpc-js/test/test-uri-parser.ts b/packages/grpc-js/test/test-uri-parser.ts new file mode 100644 index 000000000..d04cae539 --- /dev/null +++ b/packages/grpc-js/test/test-uri-parser.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2020 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. + * + */ + +import * as assert from 'assert'; +import * as uriParser from '../src/uri-parser'; +import * as resolver from '../src/resolver'; + +describe('URI Parser', function(){ + describe('parseUri', function() { + const expectationList: {target: string, result: uriParser.GrpcUri | null}[] = [ + {target: 'localhost', result: {scheme: undefined, authority: undefined, path: 'localhost'}}, + /* This looks weird, but it's OK because the resolver selection code will handle it */ + {target: 'localhost:80', result: {scheme: 'localhost', authority: undefined, path: '80'}}, + {target: 'dns:localhost', result: {scheme: 'dns', authority: undefined, path: 'localhost'}}, + {target: 'dns:///localhost', result: {scheme: 'dns', authority: '', path: 'localhost'}}, + {target: 'dns://authority/localhost', result: {scheme: 'dns', authority: 'authority', path: 'localhost'}}, + {target: '//authority/localhost', result: {scheme: undefined, authority: 'authority', path: 'localhost'}}, + // Regression test for https://github.com/grpc/grpc-node/issues/1359 + {target: 'dns:foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443', result: {scheme: 'dns', authority: undefined, path: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443'}} + ]; + for (const {target, result} of expectationList) { + it (target, function() { + assert.deepStrictEqual(uriParser.parseUri(target), result); + }); + } + }); + + describe('parseUri + mapUriDefaultScheme', function() { + const expectationList: {target: string, result: uriParser.GrpcUri | null}[] = [ + {target: 'localhost', result: {scheme: 'dns', authority: undefined, path: 'localhost'}}, + {target: 'localhost:80', result: {scheme: 'dns', authority: undefined, path: 'localhost:80'}}, + {target: 'dns:localhost', result: {scheme: 'dns', authority: undefined, path: 'localhost'}}, + {target: 'dns:///localhost', result: {scheme: 'dns', authority: '', path: 'localhost'}}, + {target: 'dns://authority/localhost', result: {scheme: 'dns', authority: 'authority', path: 'localhost'}}, + {target: 'unix:socket', result: {scheme: 'unix', authority: undefined, path: 'socket'}}, + {target: 'bad:path', result: {scheme: 'dns', authority: undefined, path: 'bad:path'}} + ]; + for (const {target, result} of expectationList) { + it(target, function() { + assert.deepStrictEqual(resolver.mapUriDefaultScheme(uriParser.parseUri(target) ?? {path: 'null'}), result); + }) + } + }); + + describe('splitHostPort', function() { + const expectationList: {path: string, result: uriParser.HostPort | null}[] = [ + {path: 'localhost', result: {host: 'localhost'}}, + {path: 'localhost:123', result: {host: 'localhost', port: 123}}, + {path: '12345:6789', result: {host: '12345', port: 6789}}, + {path: '[::1]:123', result: {host: '::1', port: 123}}, + {path: '[::1]', result: {host: '::1'}}, + {path: '[', result: null}, + {path: '[123]', result: null}, + // Regression test for https://github.com/grpc/grpc-node/issues/1359 + {path: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443', result: {host: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443'}} + ]; + for (const {path, result} of expectationList) { + it(path, function() { + assert.deepStrictEqual(uriParser.splitHostPort(path), result); + }); + } + }); +}); \ No newline at end of file diff --git a/packages/grpc-js/tsconfig.json b/packages/grpc-js/tsconfig.json new file mode 100644 index 000000000..5f955a982 --- /dev/null +++ b/packages/grpc-js/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "lib": ["es2017"], + "outDir": "build", + "target": "es2017", + "module": "commonjs", + "resolveJsonModule": true, + "incremental": true, + "types": ["mocha"], + "noUnusedLocals": true + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/packages/grpc-native-core/.jshintignore b/packages/grpc-native-core/.jshintignore deleted file mode 100644 index 0a73e1e2b..000000000 --- a/packages/grpc-native-core/.jshintignore +++ /dev/null @@ -1 +0,0 @@ -**/*_pb.js \ No newline at end of file diff --git a/packages/grpc-native-core/.npmignore b/packages/grpc-native-core/.npmignore deleted file mode 100644 index e66c5e928..000000000 --- a/packages/grpc-native-core/.npmignore +++ /dev/null @@ -1 +0,0 @@ -**/BUILD \ No newline at end of file diff --git a/packages/grpc-native-core/README.md b/packages/grpc-native-core/README.md index 200b2fca3..3b300e88f 100644 --- a/packages/grpc-native-core/README.md +++ b/packages/grpc-native-core/README.md @@ -1,39 +1 @@ -[![npm](https://img.shields.io/npm/v/grpc.svg)](https://www.npmjs.com/package/grpc) -# Node.js gRPC Library - -## PREREQUISITES -- `node`: This requires `node` to be installed, version `4.0` or above. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package. - -- **Note:** If you installed `node` via a package manager and the version is still less than `4.0`, try directly installing it from [nodejs.org](https://nodejs.org). - -## INSTALLATION - -Install the gRPC NPM package - -```sh -npm install grpc -``` - -## BUILD FROM SOURCE - 1. Clone [the grpc Git Repository](https://github.com/grpc/grpc). - 2. Run `npm install --build-from-source` from the repository root. - - - **Note:** On Windows, this might fail due to [nodejs issue #4932](https://github.com/nodejs/node/issues/4932) in which case, you will see something like the following in `npm install`'s output (towards the very beginning): - - ``` - .. - Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch. - WINDOWS_BUILD_WARNING - "..\IMPORTANT: Due to https:\github.com\nodejs\node\issues\4932, to build this library on Windows, you must first remove C:\Users\jenkins\.node-gyp\4.4.0\include\node\openssl" - ... - .. - ``` - - To fix this, you will have to delete the folder `C:\Users\\.node-gyp\\include\node\openssl` and retry `npm install` - -## API DOCUMENTATION - -See the [API Documentation](https://grpc.io/grpc/node/). - -## TESTING -To run the test suite, simply run `npm test` in the install location. +The `grpc` npm pacakge previously lived in this package. It has been removed. It now lives in the [`grpc@1.24.x` branch](https://github.com/grpc/grpc-node/tree/grpc%401.24.x/packages/grpc-native-core) where bugfixes are still being applied. \ No newline at end of file diff --git a/packages/grpc-native-core/binding.gyp b/packages/grpc-native-core/binding.gyp deleted file mode 100644 index e2c3a7435..000000000 --- a/packages/grpc-native-core/binding.gyp +++ /dev/null @@ -1,997 +0,0 @@ -# GRPC Node gyp file -# This currently builds the Node extension and dependencies -# This file has been automatically generated from a template file. -# Please look at the templates directory instead. -# This file can be regenerated from the template by running -# tools/buildgen/generate_projects.sh - -# 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. - -# Some of this file is built with the help of -# https://n8.io/converting-a-c-library-to-gyp/ -{ - 'variables': { - 'runtime%': 'node', - # Some Node installations use the system installation of OpenSSL, and on - # some systems, the system OpenSSL still does not have ALPN support. This - # will let users recompile gRPC to work without ALPN. - 'grpc_alpn%': 'true', - # Indicates that the library should be built with gcov. - 'grpc_gcov%': 'false', - # Indicates that the library should be built with compatibility for musl - # libc, so that it can run on Alpine Linux. This is only necessary if not - # building on Alpine Linux - 'grpc_alpine%': 'false' - }, - 'target_defaults': { - 'configurations': { - 'Release': { - 'cflags': [ - '-O2', - ], - 'defines': [ - 'NDEBUG', - ], - }, - 'Debug': { - 'cflags': [ - '-O0', - ], - 'defines': [ - '_DEBUG', - 'DEBUG', - ], - }, - }, - 'cflags': [ - '-g', - '-Wall', - '-Wextra', - '-Werror', - '-Wno-long-long', - '-Wno-unused-parameter', - '-DOSATOMIC_USE_INLINED=1', - ], - 'ldflags': [ - '-g', - ], - 'cflags_c': [ - '-Werror', - '-std=c99' - ], - 'cflags_cc': [ - '-Werror', - '-std=c++11' - ], - 'include_dirs': [ - 'deps/grpc', - 'deps/grpc/include' - ], - 'defines': [ - 'GPR_BACKWARDS_COMPATIBILITY_MODE', - 'GRPC_ARES=0', - 'GRPC_UV' - ], - 'conditions': [ - ['grpc_gcov=="true"', { - 'cflags': [ - '-O0', - '-fprofile-arcs', - '-ftest-coverage', - '-Wno-return-type', - ], - 'defines': [ - '_DEBUG', - 'DEBUG', - 'GPR_GCOV', - ], - 'ldflags': [ - '-fprofile-arcs', - '-ftest-coverage', - '-rdynamic', - ], - }], - ['grpc_alpine=="true"', { - 'defines': [ - 'GPR_MUSL_LIBC_COMPAT' - ] - }], - ['OS!="win" and runtime=="electron"', { - "defines": [ - 'OPENSSL_NO_THREADS' - ] - }], - # This is the condition for using boringssl - ['OS=="win" or runtime=="electron"', { - "include_dirs": [ - "deps/grpc/third_party/boringssl/include" - ], - "defines": [ - 'OPENSSL_NO_ASM' - ] - }, { - 'conditions': [ - ["target_arch=='ia32'", { - "include_dirs": [ "<(node_root_dir)/deps/openssl/config/piii" ] - }], - ["target_arch=='x64'", { - "include_dirs": [ "<(node_root_dir)/deps/openssl/config/k8" ] - }], - ["target_arch=='arm'", { - "include_dirs": [ "<(node_root_dir)/deps/openssl/config/arm" ] - }], - ['grpc_alpn=="true"', { - 'defines': [ - 'TSI_OPENSSL_ALPN_SUPPORT=1' - ], - }, { - 'defines': [ - 'TSI_OPENSSL_ALPN_SUPPORT=0' - ], - }] - ], - 'include_dirs': [ - '<(node_root_dir)/deps/openssl/openssl/include', - ] - }], - ['OS == "win"', { - "include_dirs": [ - "deps/grpc/third_party/zlib", - "deps/grpc/third_party/cares/cares" - ], - "defines": [ - '_WIN32_WINNT=0x0600', - 'WIN32_LEAN_AND_MEAN', - '_HAS_EXCEPTIONS=0', - 'UNICODE', - '_UNICODE', - 'NOMINMAX', - ], - "msvs_settings": { - 'VCCLCompilerTool': { - 'RuntimeLibrary': 1, # static debug - } - }, - "libraries": [ - "ws2_32" - ] - }, { # OS != "win" - 'include_dirs': [ - '<(node_root_dir)/deps/zlib', - '<(node_root_dir)/deps/cares/include' - ] - }], - ['OS == "mac"', { - 'xcode_settings': { - 'OTHER_CFLAGS': [ - '-g', - '-Wall', - '-Wextra', - '-Werror', - '-Wno-long-long', - '-Wno-unused-parameter', - '-DOSATOMIC_USE_INLINED=1', - ], - 'OTHER_CPLUSPLUSFLAGS': [ - '-g', - '-Wall', - '-Wextra', - '-Werror', - '-Wno-long-long', - '-Wno-unused-parameter', - '-DOSATOMIC_USE_INLINED=1', - '-stdlib=libc++', - '-std=c++11', - '-Wno-error=deprecated-declarations' - ], - }, - }] - ] - }, - 'conditions': [ - ['OS=="win" or runtime=="electron"', { - 'targets': [ - { - 'target_name': 'boringssl', - 'product_prefix': 'lib', - 'type': 'static_library', - 'cflags': [ - '-Wno-implicit-fallthrough' - ], - 'dependencies': [ - ], - 'sources': [ - 'deps/grpc/src/boringssl/err_data.c', - 'deps/grpc/third_party/boringssl/crypto/aes/aes.c', - 'deps/grpc/third_party/boringssl/crypto/aes/key_wrap.c', - 'deps/grpc/third_party/boringssl/crypto/aes/mode_wrappers.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_bitstr.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_bool.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_d2i_fp.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_dup.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_enum.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_gentm.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_i2d_fp.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_int.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_mbstr.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_object.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_octet.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_print.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_strnid.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_time.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_type.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_utctm.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/a_utf8.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/asn1_lib.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/asn1_par.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/asn_pack.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/f_enum.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/f_int.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/f_string.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/t_bitst.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/tasn_dec.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/tasn_enc.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/tasn_fre.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/tasn_new.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/tasn_typ.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/tasn_utl.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/time_support.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/x_bignum.c', - 'deps/grpc/third_party/boringssl/crypto/asn1/x_long.c', - 'deps/grpc/third_party/boringssl/crypto/base64/base64.c', - 'deps/grpc/third_party/boringssl/crypto/bio/bio.c', - 'deps/grpc/third_party/boringssl/crypto/bio/bio_mem.c', - 'deps/grpc/third_party/boringssl/crypto/bio/connect.c', - 'deps/grpc/third_party/boringssl/crypto/bio/fd.c', - 'deps/grpc/third_party/boringssl/crypto/bio/file.c', - 'deps/grpc/third_party/boringssl/crypto/bio/hexdump.c', - 'deps/grpc/third_party/boringssl/crypto/bio/pair.c', - 'deps/grpc/third_party/boringssl/crypto/bio/printf.c', - 'deps/grpc/third_party/boringssl/crypto/bio/socket.c', - 'deps/grpc/third_party/boringssl/crypto/bio/socket_helper.c', - 'deps/grpc/third_party/boringssl/crypto/bn/add.c', - 'deps/grpc/third_party/boringssl/crypto/bn/asm/x86_64-gcc.c', - 'deps/grpc/third_party/boringssl/crypto/bn/bn.c', - 'deps/grpc/third_party/boringssl/crypto/bn/bn_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/bn/cmp.c', - 'deps/grpc/third_party/boringssl/crypto/bn/convert.c', - 'deps/grpc/third_party/boringssl/crypto/bn/ctx.c', - 'deps/grpc/third_party/boringssl/crypto/bn/div.c', - 'deps/grpc/third_party/boringssl/crypto/bn/exponentiation.c', - 'deps/grpc/third_party/boringssl/crypto/bn/gcd.c', - 'deps/grpc/third_party/boringssl/crypto/bn/generic.c', - 'deps/grpc/third_party/boringssl/crypto/bn/kronecker.c', - 'deps/grpc/third_party/boringssl/crypto/bn/montgomery.c', - 'deps/grpc/third_party/boringssl/crypto/bn/montgomery_inv.c', - 'deps/grpc/third_party/boringssl/crypto/bn/mul.c', - 'deps/grpc/third_party/boringssl/crypto/bn/prime.c', - 'deps/grpc/third_party/boringssl/crypto/bn/random.c', - 'deps/grpc/third_party/boringssl/crypto/bn/rsaz_exp.c', - 'deps/grpc/third_party/boringssl/crypto/bn/shift.c', - 'deps/grpc/third_party/boringssl/crypto/bn/sqrt.c', - 'deps/grpc/third_party/boringssl/crypto/buf/buf.c', - 'deps/grpc/third_party/boringssl/crypto/bytestring/asn1_compat.c', - 'deps/grpc/third_party/boringssl/crypto/bytestring/ber.c', - 'deps/grpc/third_party/boringssl/crypto/bytestring/cbb.c', - 'deps/grpc/third_party/boringssl/crypto/bytestring/cbs.c', - 'deps/grpc/third_party/boringssl/crypto/chacha/chacha.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/aead.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/cipher.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/derive_key.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/e_aes.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/e_chacha20poly1305.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/e_des.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/e_null.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/e_rc2.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/e_rc4.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/e_ssl3.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/e_tls.c', - 'deps/grpc/third_party/boringssl/crypto/cipher/tls_cbc.c', - 'deps/grpc/third_party/boringssl/crypto/cmac/cmac.c', - 'deps/grpc/third_party/boringssl/crypto/conf/conf.c', - 'deps/grpc/third_party/boringssl/crypto/cpu-aarch64-linux.c', - 'deps/grpc/third_party/boringssl/crypto/cpu-arm-linux.c', - 'deps/grpc/third_party/boringssl/crypto/cpu-arm.c', - 'deps/grpc/third_party/boringssl/crypto/cpu-intel.c', - 'deps/grpc/third_party/boringssl/crypto/cpu-ppc64le.c', - 'deps/grpc/third_party/boringssl/crypto/crypto.c', - 'deps/grpc/third_party/boringssl/crypto/curve25519/curve25519.c', - 'deps/grpc/third_party/boringssl/crypto/curve25519/spake25519.c', - 'deps/grpc/third_party/boringssl/crypto/curve25519/x25519-x86_64.c', - 'deps/grpc/third_party/boringssl/crypto/des/des.c', - 'deps/grpc/third_party/boringssl/crypto/dh/check.c', - 'deps/grpc/third_party/boringssl/crypto/dh/dh.c', - 'deps/grpc/third_party/boringssl/crypto/dh/dh_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/dh/params.c', - 'deps/grpc/third_party/boringssl/crypto/digest/digest.c', - 'deps/grpc/third_party/boringssl/crypto/digest/digests.c', - 'deps/grpc/third_party/boringssl/crypto/dsa/dsa.c', - 'deps/grpc/third_party/boringssl/crypto/dsa/dsa_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/ec/ec.c', - 'deps/grpc/third_party/boringssl/crypto/ec/ec_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/ec/ec_key.c', - 'deps/grpc/third_party/boringssl/crypto/ec/ec_montgomery.c', - 'deps/grpc/third_party/boringssl/crypto/ec/oct.c', - 'deps/grpc/third_party/boringssl/crypto/ec/p224-64.c', - 'deps/grpc/third_party/boringssl/crypto/ec/p256-64.c', - 'deps/grpc/third_party/boringssl/crypto/ec/p256-x86_64.c', - 'deps/grpc/third_party/boringssl/crypto/ec/simple.c', - 'deps/grpc/third_party/boringssl/crypto/ec/util-64.c', - 'deps/grpc/third_party/boringssl/crypto/ec/wnaf.c', - 'deps/grpc/third_party/boringssl/crypto/ecdh/ecdh.c', - 'deps/grpc/third_party/boringssl/crypto/ecdsa/ecdsa.c', - 'deps/grpc/third_party/boringssl/crypto/ecdsa/ecdsa_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/engine/engine.c', - 'deps/grpc/third_party/boringssl/crypto/err/err.c', - 'deps/grpc/third_party/boringssl/crypto/evp/digestsign.c', - 'deps/grpc/third_party/boringssl/crypto/evp/evp.c', - 'deps/grpc/third_party/boringssl/crypto/evp/evp_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/evp/evp_ctx.c', - 'deps/grpc/third_party/boringssl/crypto/evp/p_dsa_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/evp/p_ec.c', - 'deps/grpc/third_party/boringssl/crypto/evp/p_ec_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/evp/p_rsa.c', - 'deps/grpc/third_party/boringssl/crypto/evp/p_rsa_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/evp/pbkdf.c', - 'deps/grpc/third_party/boringssl/crypto/evp/print.c', - 'deps/grpc/third_party/boringssl/crypto/evp/sign.c', - 'deps/grpc/third_party/boringssl/crypto/ex_data.c', - 'deps/grpc/third_party/boringssl/crypto/hkdf/hkdf.c', - 'deps/grpc/third_party/boringssl/crypto/hmac/hmac.c', - 'deps/grpc/third_party/boringssl/crypto/lhash/lhash.c', - 'deps/grpc/third_party/boringssl/crypto/md4/md4.c', - 'deps/grpc/third_party/boringssl/crypto/md5/md5.c', - 'deps/grpc/third_party/boringssl/crypto/mem.c', - 'deps/grpc/third_party/boringssl/crypto/modes/cbc.c', - 'deps/grpc/third_party/boringssl/crypto/modes/cfb.c', - 'deps/grpc/third_party/boringssl/crypto/modes/ctr.c', - 'deps/grpc/third_party/boringssl/crypto/modes/gcm.c', - 'deps/grpc/third_party/boringssl/crypto/modes/ofb.c', - 'deps/grpc/third_party/boringssl/crypto/modes/polyval.c', - 'deps/grpc/third_party/boringssl/crypto/obj/obj.c', - 'deps/grpc/third_party/boringssl/crypto/obj/obj_xref.c', - 'deps/grpc/third_party/boringssl/crypto/pem/pem_all.c', - 'deps/grpc/third_party/boringssl/crypto/pem/pem_info.c', - 'deps/grpc/third_party/boringssl/crypto/pem/pem_lib.c', - 'deps/grpc/third_party/boringssl/crypto/pem/pem_oth.c', - 'deps/grpc/third_party/boringssl/crypto/pem/pem_pk8.c', - 'deps/grpc/third_party/boringssl/crypto/pem/pem_pkey.c', - 'deps/grpc/third_party/boringssl/crypto/pem/pem_x509.c', - 'deps/grpc/third_party/boringssl/crypto/pem/pem_xaux.c', - 'deps/grpc/third_party/boringssl/crypto/pkcs8/p5_pbev2.c', - 'deps/grpc/third_party/boringssl/crypto/pkcs8/p8_pkey.c', - 'deps/grpc/third_party/boringssl/crypto/pkcs8/pkcs8.c', - 'deps/grpc/third_party/boringssl/crypto/poly1305/poly1305.c', - 'deps/grpc/third_party/boringssl/crypto/poly1305/poly1305_arm.c', - 'deps/grpc/third_party/boringssl/crypto/poly1305/poly1305_vec.c', - 'deps/grpc/third_party/boringssl/crypto/pool/pool.c', - 'deps/grpc/third_party/boringssl/crypto/rand/deterministic.c', - 'deps/grpc/third_party/boringssl/crypto/rand/fuchsia.c', - 'deps/grpc/third_party/boringssl/crypto/rand/rand.c', - 'deps/grpc/third_party/boringssl/crypto/rand/urandom.c', - 'deps/grpc/third_party/boringssl/crypto/rand/windows.c', - 'deps/grpc/third_party/boringssl/crypto/rc4/rc4.c', - 'deps/grpc/third_party/boringssl/crypto/refcount_c11.c', - 'deps/grpc/third_party/boringssl/crypto/refcount_lock.c', - 'deps/grpc/third_party/boringssl/crypto/rsa/blinding.c', - 'deps/grpc/third_party/boringssl/crypto/rsa/padding.c', - 'deps/grpc/third_party/boringssl/crypto/rsa/rsa.c', - 'deps/grpc/third_party/boringssl/crypto/rsa/rsa_asn1.c', - 'deps/grpc/third_party/boringssl/crypto/rsa/rsa_impl.c', - 'deps/grpc/third_party/boringssl/crypto/sha/sha1-altivec.c', - 'deps/grpc/third_party/boringssl/crypto/sha/sha1.c', - 'deps/grpc/third_party/boringssl/crypto/sha/sha256.c', - 'deps/grpc/third_party/boringssl/crypto/sha/sha512.c', - 'deps/grpc/third_party/boringssl/crypto/stack/stack.c', - 'deps/grpc/third_party/boringssl/crypto/thread.c', - 'deps/grpc/third_party/boringssl/crypto/thread_none.c', - 'deps/grpc/third_party/boringssl/crypto/thread_pthread.c', - 'deps/grpc/third_party/boringssl/crypto/thread_win.c', - 'deps/grpc/third_party/boringssl/crypto/x509/a_digest.c', - 'deps/grpc/third_party/boringssl/crypto/x509/a_sign.c', - 'deps/grpc/third_party/boringssl/crypto/x509/a_strex.c', - 'deps/grpc/third_party/boringssl/crypto/x509/a_verify.c', - 'deps/grpc/third_party/boringssl/crypto/x509/algorithm.c', - 'deps/grpc/third_party/boringssl/crypto/x509/asn1_gen.c', - 'deps/grpc/third_party/boringssl/crypto/x509/by_dir.c', - 'deps/grpc/third_party/boringssl/crypto/x509/by_file.c', - 'deps/grpc/third_party/boringssl/crypto/x509/i2d_pr.c', - 'deps/grpc/third_party/boringssl/crypto/x509/pkcs7.c', - 'deps/grpc/third_party/boringssl/crypto/x509/rsa_pss.c', - 'deps/grpc/third_party/boringssl/crypto/x509/t_crl.c', - 'deps/grpc/third_party/boringssl/crypto/x509/t_req.c', - 'deps/grpc/third_party/boringssl/crypto/x509/t_x509.c', - 'deps/grpc/third_party/boringssl/crypto/x509/t_x509a.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_att.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_cmp.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_d2.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_def.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_ext.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_lu.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_obj.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_r2x.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_req.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_set.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_trs.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_txt.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_v3.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_vfy.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509_vpm.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509cset.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509name.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509rset.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509spki.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x509type.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_algor.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_all.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_attrib.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_crl.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_exten.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_info.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_name.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_pkey.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_pubkey.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_req.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_sig.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_spki.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_val.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_x509.c', - 'deps/grpc/third_party/boringssl/crypto/x509/x_x509a.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/pcy_cache.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/pcy_data.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/pcy_lib.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/pcy_map.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/pcy_node.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/pcy_tree.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_akey.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_akeya.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_alt.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_bcons.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_bitst.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_conf.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_cpols.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_crld.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_enum.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_extku.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_genn.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_ia5.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_info.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_int.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_lib.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_ncons.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_pci.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_pcia.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_pcons.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_pku.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_pmaps.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_prn.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_purp.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_skey.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_sxnet.c', - 'deps/grpc/third_party/boringssl/crypto/x509v3/v3_utl.c', - 'deps/grpc/third_party/boringssl/ssl/bio_ssl.c', - 'deps/grpc/third_party/boringssl/ssl/custom_extensions.c', - 'deps/grpc/third_party/boringssl/ssl/d1_both.c', - 'deps/grpc/third_party/boringssl/ssl/d1_lib.c', - 'deps/grpc/third_party/boringssl/ssl/d1_pkt.c', - 'deps/grpc/third_party/boringssl/ssl/d1_srtp.c', - 'deps/grpc/third_party/boringssl/ssl/dtls_method.c', - 'deps/grpc/third_party/boringssl/ssl/dtls_record.c', - 'deps/grpc/third_party/boringssl/ssl/handshake_client.c', - 'deps/grpc/third_party/boringssl/ssl/handshake_server.c', - 'deps/grpc/third_party/boringssl/ssl/s3_both.c', - 'deps/grpc/third_party/boringssl/ssl/s3_lib.c', - 'deps/grpc/third_party/boringssl/ssl/s3_pkt.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_aead_ctx.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_asn1.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_buffer.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_cert.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_cipher.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_ecdh.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_file.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_lib.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_privkey.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_privkey_cc.cc', - 'deps/grpc/third_party/boringssl/ssl/ssl_session.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_stat.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_transcript.c', - 'deps/grpc/third_party/boringssl/ssl/ssl_x509.c', - 'deps/grpc/third_party/boringssl/ssl/t1_enc.c', - 'deps/grpc/third_party/boringssl/ssl/t1_lib.c', - 'deps/grpc/third_party/boringssl/ssl/tls13_both.c', - 'deps/grpc/third_party/boringssl/ssl/tls13_client.c', - 'deps/grpc/third_party/boringssl/ssl/tls13_enc.c', - 'deps/grpc/third_party/boringssl/ssl/tls13_server.c', - 'deps/grpc/third_party/boringssl/ssl/tls_method.c', - 'deps/grpc/third_party/boringssl/ssl/tls_record.c', - ], - 'conditions': [ - ['OS == "mac"', { - 'xcode_settings': { - 'MACOSX_DEPLOYMENT_TARGET': '10.9' - } - }] - ] - }, - ], - }], - ['OS == "win" and runtime!="electron"', { - 'targets': [ - { - # IMPORTANT WINDOWS BUILD INFORMATION - # This library does not build on Windows without modifying the Node - # development packages that node-gyp downloads in order to build. - # Due to https://github.com/nodejs/node/issues/4932, the headers for - # BoringSSL conflict with the OpenSSL headers included by default - # when including the Node headers. The remedy for this is to remove - # the OpenSSL headers, from the downloaded Node development package, - # which is typically located in `.node-gyp` in your home directory. - # - # This is not true of Electron, which does not have OpenSSL headers. - 'target_name': 'WINDOWS_BUILD_WARNING', - 'rules': [ - { - 'rule_name': 'WINDOWS_BUILD_WARNING', - 'extension': 'S', - 'inputs': [ - 'package.json' - ], - 'outputs': [ - 'ignore_this_part' - ], - 'action': ['echo', 'IMPORTANT: Due to https://github.com/nodejs/node/issues/4932, to build this library on Windows, you must first remove <(node_root_dir)/include/node/openssl/'] - } - ] - }, - ] - }], - ['OS == "win"', { - 'targets': [ - # Only want to compile zlib under Windows - { - 'target_name': 'z', - 'product_prefix': 'lib', - 'type': 'static_library', - 'dependencies': [ - ], - 'sources': [ - 'deps/grpc/third_party/zlib/adler32.c', - 'deps/grpc/third_party/zlib/compress.c', - 'deps/grpc/third_party/zlib/crc32.c', - 'deps/grpc/third_party/zlib/deflate.c', - 'deps/grpc/third_party/zlib/gzclose.c', - 'deps/grpc/third_party/zlib/gzlib.c', - 'deps/grpc/third_party/zlib/gzread.c', - 'deps/grpc/third_party/zlib/gzwrite.c', - 'deps/grpc/third_party/zlib/infback.c', - 'deps/grpc/third_party/zlib/inffast.c', - 'deps/grpc/third_party/zlib/inflate.c', - 'deps/grpc/third_party/zlib/inftrees.c', - 'deps/grpc/third_party/zlib/trees.c', - 'deps/grpc/third_party/zlib/uncompr.c', - 'deps/grpc/third_party/zlib/zutil.c', - ] - }, - ] - }] - ], - 'targets': [ - { - 'target_name': 'gpr', - 'product_prefix': 'lib', - 'type': 'static_library', - 'dependencies': [ - ], - 'sources': [ - 'deps/grpc/src/core/lib/profiling/basic_timers.c', - 'deps/grpc/src/core/lib/profiling/stap_timers.c', - 'deps/grpc/src/core/lib/support/alloc.c', - 'deps/grpc/src/core/lib/support/arena.c', - 'deps/grpc/src/core/lib/support/atm.c', - 'deps/grpc/src/core/lib/support/avl.c', - 'deps/grpc/src/core/lib/support/backoff.c', - 'deps/grpc/src/core/lib/support/cmdline.c', - 'deps/grpc/src/core/lib/support/cpu_iphone.c', - 'deps/grpc/src/core/lib/support/cpu_linux.c', - 'deps/grpc/src/core/lib/support/cpu_posix.c', - 'deps/grpc/src/core/lib/support/cpu_windows.c', - 'deps/grpc/src/core/lib/support/env_linux.c', - 'deps/grpc/src/core/lib/support/env_posix.c', - 'deps/grpc/src/core/lib/support/env_windows.c', - 'deps/grpc/src/core/lib/support/fork.c', - 'deps/grpc/src/core/lib/support/histogram.c', - 'deps/grpc/src/core/lib/support/host_port.c', - 'deps/grpc/src/core/lib/support/log.c', - 'deps/grpc/src/core/lib/support/log_android.c', - 'deps/grpc/src/core/lib/support/log_linux.c', - 'deps/grpc/src/core/lib/support/log_posix.c', - 'deps/grpc/src/core/lib/support/log_windows.c', - 'deps/grpc/src/core/lib/support/mpscq.c', - 'deps/grpc/src/core/lib/support/murmur_hash.c', - 'deps/grpc/src/core/lib/support/stack_lockfree.c', - 'deps/grpc/src/core/lib/support/string.c', - 'deps/grpc/src/core/lib/support/string_posix.c', - 'deps/grpc/src/core/lib/support/string_util_windows.c', - 'deps/grpc/src/core/lib/support/string_windows.c', - 'deps/grpc/src/core/lib/support/subprocess_posix.c', - 'deps/grpc/src/core/lib/support/subprocess_windows.c', - 'deps/grpc/src/core/lib/support/sync.c', - 'deps/grpc/src/core/lib/support/sync_posix.c', - 'deps/grpc/src/core/lib/support/sync_windows.c', - 'deps/grpc/src/core/lib/support/thd.c', - 'deps/grpc/src/core/lib/support/thd_posix.c', - 'deps/grpc/src/core/lib/support/thd_windows.c', - 'deps/grpc/src/core/lib/support/time.c', - 'deps/grpc/src/core/lib/support/time_posix.c', - 'deps/grpc/src/core/lib/support/time_precise.c', - 'deps/grpc/src/core/lib/support/time_windows.c', - 'deps/grpc/src/core/lib/support/tls_pthread.c', - 'deps/grpc/src/core/lib/support/tmpfile_msys.c', - 'deps/grpc/src/core/lib/support/tmpfile_posix.c', - 'deps/grpc/src/core/lib/support/tmpfile_windows.c', - 'deps/grpc/src/core/lib/support/wrap_memcpy.c', - ], - 'conditions': [ - ['OS == "mac"', { - 'xcode_settings': { - 'MACOSX_DEPLOYMENT_TARGET': '10.9' - } - }] - ] - }, - { - 'target_name': 'grpc', - 'product_prefix': 'lib', - 'type': 'static_library', - 'dependencies': [ - 'gpr', - ], - 'sources': [ - 'deps/grpc/src/core/lib/surface/init.c', - 'deps/grpc/src/core/lib/channel/channel_args.c', - 'deps/grpc/src/core/lib/channel/channel_stack.c', - 'deps/grpc/src/core/lib/channel/channel_stack_builder.c', - 'deps/grpc/src/core/lib/channel/connected_channel.c', - 'deps/grpc/src/core/lib/channel/handshaker.c', - 'deps/grpc/src/core/lib/channel/handshaker_factory.c', - 'deps/grpc/src/core/lib/channel/handshaker_registry.c', - 'deps/grpc/src/core/lib/compression/compression.c', - 'deps/grpc/src/core/lib/compression/message_compress.c', - 'deps/grpc/src/core/lib/compression/stream_compression.c', - 'deps/grpc/src/core/lib/compression/stream_compression_gzip.c', - 'deps/grpc/src/core/lib/compression/stream_compression_identity.c', - 'deps/grpc/src/core/lib/debug/stats.c', - 'deps/grpc/src/core/lib/debug/stats_data.c', - 'deps/grpc/src/core/lib/http/format_request.c', - 'deps/grpc/src/core/lib/http/httpcli.c', - 'deps/grpc/src/core/lib/http/parser.c', - 'deps/grpc/src/core/lib/iomgr/call_combiner.c', - 'deps/grpc/src/core/lib/iomgr/closure.c', - 'deps/grpc/src/core/lib/iomgr/combiner.c', - 'deps/grpc/src/core/lib/iomgr/endpoint.c', - 'deps/grpc/src/core/lib/iomgr/endpoint_pair_posix.c', - 'deps/grpc/src/core/lib/iomgr/endpoint_pair_uv.c', - 'deps/grpc/src/core/lib/iomgr/endpoint_pair_windows.c', - 'deps/grpc/src/core/lib/iomgr/error.c', - 'deps/grpc/src/core/lib/iomgr/ev_epoll1_linux.c', - 'deps/grpc/src/core/lib/iomgr/ev_epollex_linux.c', - 'deps/grpc/src/core/lib/iomgr/ev_epollsig_linux.c', - 'deps/grpc/src/core/lib/iomgr/ev_poll_posix.c', - 'deps/grpc/src/core/lib/iomgr/ev_posix.c', - 'deps/grpc/src/core/lib/iomgr/ev_windows.c', - 'deps/grpc/src/core/lib/iomgr/exec_ctx.c', - 'deps/grpc/src/core/lib/iomgr/executor.c', - 'deps/grpc/src/core/lib/iomgr/fork_posix.c', - 'deps/grpc/src/core/lib/iomgr/fork_windows.c', - 'deps/grpc/src/core/lib/iomgr/gethostname_fallback.c', - 'deps/grpc/src/core/lib/iomgr/gethostname_host_name_max.c', - 'deps/grpc/src/core/lib/iomgr/gethostname_sysconf.c', - 'deps/grpc/src/core/lib/iomgr/iocp_windows.c', - 'deps/grpc/src/core/lib/iomgr/iomgr.c', - 'deps/grpc/src/core/lib/iomgr/iomgr_posix.c', - 'deps/grpc/src/core/lib/iomgr/iomgr_uv.c', - 'deps/grpc/src/core/lib/iomgr/iomgr_windows.c', - 'deps/grpc/src/core/lib/iomgr/is_epollexclusive_available.c', - 'deps/grpc/src/core/lib/iomgr/load_file.c', - 'deps/grpc/src/core/lib/iomgr/lockfree_event.c', - 'deps/grpc/src/core/lib/iomgr/network_status_tracker.c', - 'deps/grpc/src/core/lib/iomgr/polling_entity.c', - 'deps/grpc/src/core/lib/iomgr/pollset_set_uv.c', - 'deps/grpc/src/core/lib/iomgr/pollset_set_windows.c', - 'deps/grpc/src/core/lib/iomgr/pollset_uv.c', - 'deps/grpc/src/core/lib/iomgr/pollset_windows.c', - 'deps/grpc/src/core/lib/iomgr/resolve_address_posix.c', - 'deps/grpc/src/core/lib/iomgr/resolve_address_uv.c', - 'deps/grpc/src/core/lib/iomgr/resolve_address_windows.c', - 'deps/grpc/src/core/lib/iomgr/resource_quota.c', - 'deps/grpc/src/core/lib/iomgr/sockaddr_utils.c', - 'deps/grpc/src/core/lib/iomgr/socket_factory_posix.c', - 'deps/grpc/src/core/lib/iomgr/socket_mutator.c', - 'deps/grpc/src/core/lib/iomgr/socket_utils_common_posix.c', - 'deps/grpc/src/core/lib/iomgr/socket_utils_linux.c', - 'deps/grpc/src/core/lib/iomgr/socket_utils_posix.c', - 'deps/grpc/src/core/lib/iomgr/socket_utils_uv.c', - 'deps/grpc/src/core/lib/iomgr/socket_utils_windows.c', - 'deps/grpc/src/core/lib/iomgr/socket_windows.c', - 'deps/grpc/src/core/lib/iomgr/tcp_client_posix.c', - 'deps/grpc/src/core/lib/iomgr/tcp_client_uv.c', - 'deps/grpc/src/core/lib/iomgr/tcp_client_windows.c', - 'deps/grpc/src/core/lib/iomgr/tcp_posix.c', - 'deps/grpc/src/core/lib/iomgr/tcp_server_posix.c', - 'deps/grpc/src/core/lib/iomgr/tcp_server_utils_posix_common.c', - 'deps/grpc/src/core/lib/iomgr/tcp_server_utils_posix_ifaddrs.c', - 'deps/grpc/src/core/lib/iomgr/tcp_server_utils_posix_noifaddrs.c', - 'deps/grpc/src/core/lib/iomgr/tcp_server_uv.c', - 'deps/grpc/src/core/lib/iomgr/tcp_server_windows.c', - 'deps/grpc/src/core/lib/iomgr/tcp_uv.c', - 'deps/grpc/src/core/lib/iomgr/tcp_windows.c', - 'deps/grpc/src/core/lib/iomgr/time_averaged_stats.c', - 'deps/grpc/src/core/lib/iomgr/timer_generic.c', - 'deps/grpc/src/core/lib/iomgr/timer_heap.c', - 'deps/grpc/src/core/lib/iomgr/timer_manager.c', - 'deps/grpc/src/core/lib/iomgr/timer_uv.c', - 'deps/grpc/src/core/lib/iomgr/udp_server.c', - 'deps/grpc/src/core/lib/iomgr/unix_sockets_posix.c', - 'deps/grpc/src/core/lib/iomgr/unix_sockets_posix_noop.c', - 'deps/grpc/src/core/lib/iomgr/wakeup_fd_cv.c', - 'deps/grpc/src/core/lib/iomgr/wakeup_fd_eventfd.c', - 'deps/grpc/src/core/lib/iomgr/wakeup_fd_nospecial.c', - 'deps/grpc/src/core/lib/iomgr/wakeup_fd_pipe.c', - 'deps/grpc/src/core/lib/iomgr/wakeup_fd_posix.c', - 'deps/grpc/src/core/lib/json/json.c', - 'deps/grpc/src/core/lib/json/json_reader.c', - 'deps/grpc/src/core/lib/json/json_string.c', - 'deps/grpc/src/core/lib/json/json_writer.c', - 'deps/grpc/src/core/lib/slice/b64.c', - 'deps/grpc/src/core/lib/slice/percent_encoding.c', - 'deps/grpc/src/core/lib/slice/slice.c', - 'deps/grpc/src/core/lib/slice/slice_buffer.c', - 'deps/grpc/src/core/lib/slice/slice_hash_table.c', - 'deps/grpc/src/core/lib/slice/slice_intern.c', - 'deps/grpc/src/core/lib/slice/slice_string_helpers.c', - 'deps/grpc/src/core/lib/surface/alarm.c', - 'deps/grpc/src/core/lib/surface/api_trace.c', - 'deps/grpc/src/core/lib/surface/byte_buffer.c', - 'deps/grpc/src/core/lib/surface/byte_buffer_reader.c', - 'deps/grpc/src/core/lib/surface/call.c', - 'deps/grpc/src/core/lib/surface/call_details.c', - 'deps/grpc/src/core/lib/surface/call_log_batch.c', - 'deps/grpc/src/core/lib/surface/channel.c', - 'deps/grpc/src/core/lib/surface/channel_init.c', - 'deps/grpc/src/core/lib/surface/channel_ping.c', - 'deps/grpc/src/core/lib/surface/channel_stack_type.c', - 'deps/grpc/src/core/lib/surface/completion_queue.c', - 'deps/grpc/src/core/lib/surface/completion_queue_factory.c', - 'deps/grpc/src/core/lib/surface/event_string.c', - 'deps/grpc/src/core/lib/surface/lame_client.cc', - 'deps/grpc/src/core/lib/surface/metadata_array.c', - 'deps/grpc/src/core/lib/surface/server.c', - 'deps/grpc/src/core/lib/surface/validate_metadata.c', - 'deps/grpc/src/core/lib/surface/version.c', - 'deps/grpc/src/core/lib/transport/bdp_estimator.c', - 'deps/grpc/src/core/lib/transport/byte_stream.c', - 'deps/grpc/src/core/lib/transport/connectivity_state.c', - 'deps/grpc/src/core/lib/transport/error_utils.c', - 'deps/grpc/src/core/lib/transport/metadata.c', - 'deps/grpc/src/core/lib/transport/metadata_batch.c', - 'deps/grpc/src/core/lib/transport/pid_controller.c', - 'deps/grpc/src/core/lib/transport/service_config.c', - 'deps/grpc/src/core/lib/transport/static_metadata.c', - 'deps/grpc/src/core/lib/transport/status_conversion.c', - 'deps/grpc/src/core/lib/transport/timeout_encoding.c', - 'deps/grpc/src/core/lib/transport/transport.c', - 'deps/grpc/src/core/lib/transport/transport_op_string.c', - 'deps/grpc/src/core/lib/debug/trace.c', - 'deps/grpc/src/core/ext/transport/chttp2/server/secure/server_secure_chttp2.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/bin_decoder.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/bin_encoder.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/chttp2_plugin.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/chttp2_transport.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/flow_control.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/frame_data.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/frame_goaway.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/frame_ping.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/frame_rst_stream.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/frame_settings.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/frame_window_update.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/hpack_encoder.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/hpack_parser.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/hpack_table.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/http2_settings.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/huffsyms.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/incoming_metadata.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/parsing.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/stream_lists.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/stream_map.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/varint.c', - 'deps/grpc/src/core/ext/transport/chttp2/transport/writing.c', - 'deps/grpc/src/core/ext/transport/chttp2/alpn/alpn.c', - 'deps/grpc/src/core/ext/filters/http/client/http_client_filter.c', - 'deps/grpc/src/core/ext/filters/http/http_filters_plugin.c', - 'deps/grpc/src/core/ext/filters/http/message_compress/message_compress_filter.c', - 'deps/grpc/src/core/ext/filters/http/server/http_server_filter.c', - 'deps/grpc/src/core/lib/http/httpcli_security_connector.c', - 'deps/grpc/src/core/lib/security/context/security_context.c', - 'deps/grpc/src/core/lib/security/credentials/composite/composite_credentials.c', - 'deps/grpc/src/core/lib/security/credentials/credentials.c', - 'deps/grpc/src/core/lib/security/credentials/credentials_metadata.c', - 'deps/grpc/src/core/lib/security/credentials/fake/fake_credentials.c', - 'deps/grpc/src/core/lib/security/credentials/google_default/credentials_generic.c', - 'deps/grpc/src/core/lib/security/credentials/google_default/google_default_credentials.c', - 'deps/grpc/src/core/lib/security/credentials/iam/iam_credentials.c', - 'deps/grpc/src/core/lib/security/credentials/jwt/json_token.c', - 'deps/grpc/src/core/lib/security/credentials/jwt/jwt_credentials.c', - 'deps/grpc/src/core/lib/security/credentials/jwt/jwt_verifier.c', - 'deps/grpc/src/core/lib/security/credentials/oauth2/oauth2_credentials.c', - 'deps/grpc/src/core/lib/security/credentials/plugin/plugin_credentials.c', - 'deps/grpc/src/core/lib/security/credentials/ssl/ssl_credentials.c', - 'deps/grpc/src/core/lib/security/transport/client_auth_filter.c', - 'deps/grpc/src/core/lib/security/transport/lb_targets_info.c', - 'deps/grpc/src/core/lib/security/transport/secure_endpoint.c', - 'deps/grpc/src/core/lib/security/transport/security_connector.c', - 'deps/grpc/src/core/lib/security/transport/security_handshaker.c', - 'deps/grpc/src/core/lib/security/transport/server_auth_filter.c', - 'deps/grpc/src/core/lib/security/transport/tsi_error.c', - 'deps/grpc/src/core/lib/security/util/json_util.c', - 'deps/grpc/src/core/lib/surface/init_secure.c', - 'deps/grpc/src/core/tsi/fake_transport_security.c', - 'deps/grpc/src/core/tsi/gts_transport_security.c', - 'deps/grpc/src/core/tsi/ssl_transport_security.c', - 'deps/grpc/src/core/tsi/transport_security_grpc.c', - 'deps/grpc/src/core/tsi/transport_security.c', - 'deps/grpc/src/core/tsi/transport_security_adapter.c', - 'deps/grpc/src/core/ext/transport/chttp2/server/chttp2_server.c', - 'deps/grpc/src/core/ext/transport/chttp2/client/secure/secure_channel_create.c', - 'deps/grpc/src/core/ext/filters/client_channel/channel_connectivity.c', - 'deps/grpc/src/core/ext/filters/client_channel/client_channel.c', - 'deps/grpc/src/core/ext/filters/client_channel/client_channel_factory.c', - 'deps/grpc/src/core/ext/filters/client_channel/client_channel_plugin.c', - 'deps/grpc/src/core/ext/filters/client_channel/connector.c', - 'deps/grpc/src/core/ext/filters/client_channel/http_connect_handshaker.c', - 'deps/grpc/src/core/ext/filters/client_channel/http_proxy.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy_factory.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy_registry.c', - 'deps/grpc/src/core/ext/filters/client_channel/parse_address.c', - 'deps/grpc/src/core/ext/filters/client_channel/proxy_mapper.c', - 'deps/grpc/src/core/ext/filters/client_channel/proxy_mapper_registry.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver_factory.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver_registry.c', - 'deps/grpc/src/core/ext/filters/client_channel/retry_throttle.c', - 'deps/grpc/src/core/ext/filters/client_channel/subchannel.c', - 'deps/grpc/src/core/ext/filters/client_channel/subchannel_index.c', - 'deps/grpc/src/core/ext/filters/client_channel/uri_parser.c', - 'deps/grpc/src/core/ext/filters/deadline/deadline_filter.c', - 'deps/grpc/src/core/ext/transport/chttp2/client/chttp2_connector.c', - 'deps/grpc/src/core/ext/transport/chttp2/server/insecure/server_chttp2.c', - 'deps/grpc/src/core/ext/transport/chttp2/server/insecure/server_chttp2_posix.c', - 'deps/grpc/src/core/ext/transport/chttp2/client/insecure/channel_create.c', - 'deps/grpc/src/core/ext/transport/chttp2/client/insecure/channel_create_posix.c', - 'deps/grpc/src/core/ext/transport/inproc/inproc_plugin.c', - 'deps/grpc/src/core/ext/transport/inproc/inproc_transport.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel_secure.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c', - 'deps/grpc/third_party/nanopb/pb_common.c', - 'deps/grpc/third_party/nanopb/pb_decode.c', - 'deps/grpc/third_party/nanopb/pb_encode.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.c', - 'deps/grpc/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.c', - 'deps/grpc/src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.c', - 'deps/grpc/src/core/ext/filters/load_reporting/server_load_reporting_filter.c', - 'deps/grpc/src/core/ext/filters/load_reporting/server_load_reporting_plugin.c', - 'deps/grpc/src/core/ext/census/base_resources.c', - 'deps/grpc/src/core/ext/census/context.c', - 'deps/grpc/src/core/ext/census/gen/census.pb.c', - 'deps/grpc/src/core/ext/census/gen/trace_context.pb.c', - 'deps/grpc/src/core/ext/census/grpc_context.c', - 'deps/grpc/src/core/ext/census/grpc_filter.c', - 'deps/grpc/src/core/ext/census/grpc_plugin.c', - 'deps/grpc/src/core/ext/census/initialize.c', - 'deps/grpc/src/core/ext/census/intrusive_hash_map.c', - 'deps/grpc/src/core/ext/census/mlog.c', - 'deps/grpc/src/core/ext/census/operation.c', - 'deps/grpc/src/core/ext/census/placeholders.c', - 'deps/grpc/src/core/ext/census/resource.c', - 'deps/grpc/src/core/ext/census/trace_context.c', - 'deps/grpc/src/core/ext/census/tracing.c', - 'deps/grpc/src/core/ext/filters/max_age/max_age_filter.c', - 'deps/grpc/src/core/ext/filters/message_size/message_size_filter.c', - 'deps/grpc/src/core/ext/filters/workarounds/workaround_cronet_compression_filter.c', - 'deps/grpc/src/core/ext/filters/workarounds/workaround_utils.c', - 'deps/grpc/src/core/plugin_registry/grpc_plugin_registry.c', - ], - 'conditions': [ - ['OS == "mac"', { - 'xcode_settings': { - 'MACOSX_DEPLOYMENT_TARGET': '10.9' - } - }] - ] - }, - { - 'include_dirs': [ - " - -#include -#include -#include "grpc/byte_buffer_reader.h" -#include "grpc/grpc.h" -#include "grpc/slice.h" - -#include "byte_buffer.h" -#include "slice.h" - -namespace grpc { -namespace node { - -using Nan::Callback; -using Nan::MaybeLocal; - -using v8::Function; -using v8::Local; -using v8::Object; -using v8::Number; -using v8::Value; - -grpc_byte_buffer *BufferToByteBuffer(Local buffer) { - Nan::HandleScope scope; - grpc_slice slice = CreateSliceFromBuffer(buffer); - grpc_byte_buffer *byte_buffer(grpc_raw_byte_buffer_create(&slice, 1)); - grpc_slice_unref(slice); - return byte_buffer; -} - -namespace { -void delete_buffer(char *data, void *hint) { - grpc_slice *slice = static_cast(hint); - grpc_slice_unref(*slice); - delete slice; -} -} - -Local ByteBufferToBuffer(grpc_byte_buffer *buffer) { - Nan::EscapableHandleScope scope; - if (buffer == NULL) { - return scope.Escape(Nan::Null()); - } - grpc_byte_buffer_reader reader; - if (!grpc_byte_buffer_reader_init(&reader, buffer)) { - Nan::ThrowError("Error initializing byte buffer reader."); - return scope.Escape(Nan::Undefined()); - } - grpc_slice *slice = new grpc_slice; - *slice = grpc_byte_buffer_reader_readall(&reader); - grpc_byte_buffer_reader_destroy(&reader); - char *result = reinterpret_cast(GRPC_SLICE_START_PTR(*slice)); - size_t length = GRPC_SLICE_LENGTH(*slice); - Local buf = - Nan::NewBuffer(result, length, delete_buffer, slice).ToLocalChecked(); - return scope.Escape(buf); -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/byte_buffer.h b/packages/grpc-native-core/ext/byte_buffer.h deleted file mode 100644 index 622314760..000000000 --- a/packages/grpc-native-core/ext/byte_buffer.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef NET_GRPC_NODE_BYTE_BUFFER_H_ -#define NET_GRPC_NODE_BYTE_BUFFER_H_ - -#include - -#include -#include -#include "grpc/grpc.h" - -namespace grpc { -namespace node { - -/* Convert a Node.js Buffer to grpc_byte_buffer. Requires that - ::node::Buffer::HasInstance(buffer) */ -grpc_byte_buffer *BufferToByteBuffer(v8::Local buffer); - -/* Convert a grpc_byte_buffer to a Node.js Buffer */ -v8::Local ByteBufferToBuffer(grpc_byte_buffer *buffer); - -} // namespace node -} // namespace grpc - -#endif // NET_GRPC_NODE_BYTE_BUFFER_H_ diff --git a/packages/grpc-native-core/ext/call.cc b/packages/grpc-native-core/ext/call.cc deleted file mode 100644 index 26095a78f..000000000 --- a/packages/grpc-native-core/ext/call.cc +++ /dev/null @@ -1,815 +0,0 @@ -/* - * - * 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. - * - */ - -#include -#include -#include - -#include - -#include "byte_buffer.h" -#include "call.h" -#include "call_credentials.h" -#include "channel.h" -#include "completion_queue.h" -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" -#include "grpc/support/alloc.h" -#include "grpc/support/log.h" -#include "grpc/support/time.h" -#include "slice.h" -#include "timeval.h" - -using std::unique_ptr; -using std::shared_ptr; -using std::vector; - -namespace grpc { -namespace node { - -using Nan::Callback; -using Nan::EscapableHandleScope; -using Nan::HandleScope; -using Nan::Maybe; -using Nan::MaybeLocal; -using Nan::ObjectWrap; -using Nan::Persistent; -using Nan::Utf8String; - -using v8::Array; -using v8::Boolean; -using v8::Exception; -using v8::External; -using v8::Function; -using v8::FunctionTemplate; -using v8::Integer; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::ObjectTemplate; -using v8::Uint32; -using v8::String; -using v8::Value; - -Callback *Call::constructor; -Persistent Call::fun_tpl; - -/** - * Helper function for throwing errors with a grpc_call_error value. - * Modified from the answer by Gus Goose to - * http://stackoverflow.com/questions/31794200. - */ -Local nanErrorWithCode(const char *msg, grpc_call_error code) { - EscapableHandleScope scope; - Local err = Nan::Error(msg).As(); - Nan::Set(err, Nan::New("code").ToLocalChecked(), Nan::New(code)); - return scope.Escape(err); -} - -bool CreateMetadataArray(Local metadata, grpc_metadata_array *array) { - HandleScope scope; - Local keys = Nan::GetOwnPropertyNames(metadata).ToLocalChecked(); - for (unsigned int i = 0; i < keys->Length(); i++) { - Local current_key = - Nan::To(Nan::Get(keys, i).ToLocalChecked()).ToLocalChecked(); - Local value_array = Nan::Get(metadata, current_key).ToLocalChecked(); - if (!value_array->IsArray()) { - return false; - } - array->capacity += Local::Cast(value_array)->Length(); - } - array->metadata = reinterpret_cast( - gpr_zalloc(array->capacity * sizeof(grpc_metadata))); - for (unsigned int i = 0; i < keys->Length(); i++) { - Local current_key(Nan::To(keys->Get(i)).ToLocalChecked()); - Local values = - Local::Cast(Nan::Get(metadata, current_key).ToLocalChecked()); - grpc_slice key_slice = CreateSliceFromString(current_key); - grpc_slice key_intern_slice = grpc_slice_intern(key_slice); - grpc_slice_unref(key_slice); - for (unsigned int j = 0; j < values->Length(); j++) { - Local value = Nan::Get(values, j).ToLocalChecked(); - grpc_metadata *current = &array->metadata[array->count]; - current->key = key_intern_slice; - // Only allow binary headers for "-bin" keys - if (grpc_is_binary_header(key_intern_slice)) { - if (::node::Buffer::HasInstance(value)) { - current->value = CreateSliceFromBuffer(value); - } else { - return false; - } - } else { - if (value->IsString()) { - Local string_value = Nan::To(value).ToLocalChecked(); - current->value = CreateSliceFromString(string_value); - } else { - return false; - } - } - array->count += 1; - } - } - return true; -} - -void DestroyMetadataArray(grpc_metadata_array *array) { - for (size_t i = 0; i < array->count; i++) { - // Don't unref keys because they are interned - grpc_slice_unref(array->metadata[i].value); - } - grpc_metadata_array_destroy(array); -} - -Local ParseMetadata(const grpc_metadata_array *metadata_array) { - EscapableHandleScope scope; - grpc_metadata *metadata_elements = metadata_array->metadata; - size_t length = metadata_array->count; - Local metadata_object = Nan::New(); - for (unsigned int i = 0; i < length; i++) { - grpc_metadata *elem = &metadata_elements[i]; - // TODO(murgatroid99): Use zero-copy string construction instead - Local key_string = CopyStringFromSlice(elem->key); - Local array; - MaybeLocal maybe_array = Nan::Get(metadata_object, key_string); - if (maybe_array.IsEmpty() || !maybe_array.ToLocalChecked()->IsArray()) { - array = Nan::New(0); - Nan::Set(metadata_object, key_string, array); - } else { - array = Local::Cast(maybe_array.ToLocalChecked()); - } - if (grpc_is_binary_header(elem->key)) { - Nan::Set(array, array->Length(), CreateBufferFromSlice(elem->value)); - } else { - // TODO(murgatroid99): Use zero-copy string construction instead - Nan::Set(array, array->Length(), CopyStringFromSlice(elem->value)); - } - } - return scope.Escape(metadata_object); -} - -Local Op::GetOpType() const { - EscapableHandleScope scope; - return scope.Escape(Nan::New(GetTypeString()).ToLocalChecked()); -} - -Op::~Op() {} - -class SendMetadataOp : public Op { - public: - SendMetadataOp() { grpc_metadata_array_init(&send_metadata); } - ~SendMetadataOp() { DestroyMetadataArray(&send_metadata); } - Local GetNodeValue() const { - EscapableHandleScope scope; - return scope.Escape(Nan::True()); - } - bool ParseOp(Local value, grpc_op *out) { - if (!value->IsObject()) { - return false; - } - MaybeLocal maybe_metadata = Nan::To(value); - if (maybe_metadata.IsEmpty()) { - return false; - } - if (!CreateMetadataArray(maybe_metadata.ToLocalChecked(), &send_metadata)) { - return false; - } - out->data.send_initial_metadata.count = send_metadata.count; - out->data.send_initial_metadata.metadata = send_metadata.metadata; - return true; - } - bool IsFinalOp() { return false; } - void OnComplete(bool success) {} - - protected: - std::string GetTypeString() const { return "send_metadata"; } - - private: - grpc_metadata_array send_metadata; -}; - -class SendMessageOp : public Op { - public: - SendMessageOp() { send_message = NULL; } - ~SendMessageOp() { - if (send_message != NULL) { - grpc_byte_buffer_destroy(send_message); - } - } - Local GetNodeValue() const { - EscapableHandleScope scope; - return scope.Escape(Nan::True()); - } - bool ParseOp(Local value, grpc_op *out) { - if (!::node::Buffer::HasInstance(value)) { - return false; - } - Local object_value = Nan::To(value).ToLocalChecked(); - MaybeLocal maybe_flag_value = - Nan::Get(object_value, Nan::New("grpcWriteFlags").ToLocalChecked()); - if (!maybe_flag_value.IsEmpty()) { - Local flag_value = maybe_flag_value.ToLocalChecked(); - if (flag_value->IsUint32()) { - Maybe maybe_flag = Nan::To(flag_value); - out->flags = maybe_flag.FromMaybe(0) & GRPC_WRITE_USED_MASK; - } - } - send_message = BufferToByteBuffer(value); - out->data.send_message.send_message = send_message; - return true; - } - - bool IsFinalOp() { return false; } - void OnComplete(bool success) {} - - protected: - std::string GetTypeString() const { return "send_message"; } - - private: - grpc_byte_buffer *send_message; -}; - -class SendClientCloseOp : public Op { - public: - Local GetNodeValue() const { - EscapableHandleScope scope; - return scope.Escape(Nan::True()); - } - - bool ParseOp(Local value, grpc_op *out) { return true; } - bool IsFinalOp() { return false; } - void OnComplete(bool success) {} - - protected: - std::string GetTypeString() const { return "client_close"; } -}; - -class SendServerStatusOp : public Op { - public: - SendServerStatusOp() { - details = grpc_empty_slice(); - grpc_metadata_array_init(&status_metadata); - } - ~SendServerStatusOp() { - grpc_slice_unref(details); - DestroyMetadataArray(&status_metadata); - } - Local GetNodeValue() const { - EscapableHandleScope scope; - return scope.Escape(Nan::True()); - } - bool ParseOp(Local value, grpc_op *out) { - if (!value->IsObject()) { - return false; - } - Local server_status = Nan::To(value).ToLocalChecked(); - MaybeLocal maybe_metadata = - Nan::Get(server_status, Nan::New("metadata").ToLocalChecked()); - if (maybe_metadata.IsEmpty()) { - return false; - } - if (!maybe_metadata.ToLocalChecked()->IsObject()) { - return false; - } - Local metadata = - Nan::To(maybe_metadata.ToLocalChecked()).ToLocalChecked(); - MaybeLocal maybe_code = - Nan::Get(server_status, Nan::New("code").ToLocalChecked()); - if (maybe_code.IsEmpty()) { - return false; - } - if (!maybe_code.ToLocalChecked()->IsUint32()) { - return false; - } - uint32_t code = Nan::To(maybe_code.ToLocalChecked()).FromJust(); - MaybeLocal maybe_details = - Nan::Get(server_status, Nan::New("details").ToLocalChecked()); - if (maybe_details.IsEmpty()) { - return false; - } - if (!maybe_details.ToLocalChecked()->IsString()) { - return false; - } - Local details = - Nan::To(maybe_details.ToLocalChecked()).ToLocalChecked(); - if (!CreateMetadataArray(metadata, &status_metadata)) { - return false; - } - out->data.send_status_from_server.trailing_metadata_count = - status_metadata.count; - out->data.send_status_from_server.trailing_metadata = - status_metadata.metadata; - out->data.send_status_from_server.status = - static_cast(code); - this->details = CreateSliceFromString(details); - out->data.send_status_from_server.status_details = &this->details; - return true; - } - bool IsFinalOp() { return true; } - void OnComplete(bool success) {} - - protected: - std::string GetTypeString() const { return "send_status"; } - - private: - grpc_slice details; - grpc_metadata_array status_metadata; -}; - -class GetMetadataOp : public Op { - public: - GetMetadataOp() { grpc_metadata_array_init(&recv_metadata); } - - ~GetMetadataOp() { grpc_metadata_array_destroy(&recv_metadata); } - - Local GetNodeValue() const { - EscapableHandleScope scope; - return scope.Escape(ParseMetadata(&recv_metadata)); - } - - bool ParseOp(Local value, grpc_op *out) { - out->data.recv_initial_metadata.recv_initial_metadata = &recv_metadata; - return true; - } - bool IsFinalOp() { return false; } - void OnComplete(bool success) {} - - protected: - std::string GetTypeString() const { return "metadata"; } - - private: - grpc_metadata_array recv_metadata; -}; - -class ReadMessageOp : public Op { - public: - ReadMessageOp() { recv_message = NULL; } - ~ReadMessageOp() { - if (recv_message != NULL) { - grpc_byte_buffer_destroy(recv_message); - } - } - Local GetNodeValue() const { - EscapableHandleScope scope; - return scope.Escape(ByteBufferToBuffer(recv_message)); - } - - bool ParseOp(Local value, grpc_op *out) { - out->data.recv_message.recv_message = &recv_message; - return true; - } - bool IsFinalOp() { return false; } - void OnComplete(bool success) {} - - protected: - std::string GetTypeString() const { return "read"; } - - private: - grpc_byte_buffer *recv_message; -}; - -class ClientStatusOp : public Op { - public: - ClientStatusOp() { - grpc_metadata_array_init(&metadata_array); - status_details = grpc_empty_slice(); - } - - ~ClientStatusOp() { - grpc_metadata_array_destroy(&metadata_array); - grpc_slice_unref(status_details); - } - - bool ParseOp(Local value, grpc_op *out) { - out->data.recv_status_on_client.trailing_metadata = &metadata_array; - out->data.recv_status_on_client.status = &status; - out->data.recv_status_on_client.status_details = &status_details; - return true; - } - - Local GetNodeValue() const { - EscapableHandleScope scope; - Local status_obj = Nan::New(); - Nan::Set(status_obj, Nan::New("code").ToLocalChecked(), - Nan::New(status)); - Nan::Set(status_obj, Nan::New("details").ToLocalChecked(), - CopyStringFromSlice(status_details)); - Nan::Set(status_obj, Nan::New("metadata").ToLocalChecked(), - ParseMetadata(&metadata_array)); - return scope.Escape(status_obj); - } - bool IsFinalOp() { return true; } - void OnComplete(bool success) {} - - protected: - std::string GetTypeString() const { return "status"; } - - private: - grpc_metadata_array metadata_array; - grpc_status_code status; - grpc_slice status_details; -}; - -class ServerCloseResponseOp : public Op { - public: - Local GetNodeValue() const { - EscapableHandleScope scope; - return scope.Escape(Nan::New(cancelled)); - } - - bool ParseOp(Local value, grpc_op *out) { - out->data.recv_close_on_server.cancelled = &cancelled; - return true; - } - bool IsFinalOp() { return false; } - void OnComplete(bool success) {} - - protected: - std::string GetTypeString() const { return "cancelled"; } - - private: - int cancelled; -}; - -tag::tag(Callback *callback, OpVec *ops, Call *call, Local call_value) - : callback(callback), ops(ops), call(call) { - HandleScope scope; - call_persist.Reset(call_value); -} - -tag::~tag() { - delete callback; - delete ops; -} - -void CompleteTag(void *tag, const char *error_message) { - HandleScope scope; - struct tag *tag_struct = reinterpret_cast(tag); - Callback *callback = tag_struct->callback; - if (error_message == NULL) { - Local tag_obj = Nan::New(); - for (vector >::iterator it = tag_struct->ops->begin(); - it != tag_struct->ops->end(); ++it) { - Op *op_ptr = it->get(); - Nan::Set(tag_obj, op_ptr->GetOpType(), op_ptr->GetNodeValue()); - } - Local argv[] = {Nan::Null(), tag_obj}; - callback->Call(2, argv); - } else { - Local argv[] = {Nan::Error(error_message)}; - callback->Call(1, argv); - } - bool success = (error_message == NULL); - bool is_final_op = false; - for (vector >::iterator it = tag_struct->ops->begin(); - it != tag_struct->ops->end(); ++it) { - Op *op_ptr = it->get(); - op_ptr->OnComplete(success); - if (op_ptr->IsFinalOp()) { - is_final_op = true; - } - } - if (tag_struct->call == NULL) { - return; - } - tag_struct->call->CompleteBatch(is_final_op); -} - -void DestroyTag(void *tag) { - struct tag *tag_struct = reinterpret_cast(tag); - delete tag_struct; -} - -void Call::DestroyCall() { - if (this->wrapped_call != NULL) { - grpc_call_unref(this->wrapped_call); - this->wrapped_call = NULL; - } -} - -Call::Call(grpc_call *call) - : wrapped_call(call), pending_batches(0), has_final_op_completed(false) { - peer = grpc_call_get_peer(call); -} - -Call::~Call() { - DestroyCall(); - gpr_free(peer); -} - -void Call::Init(Local exports) { - HandleScope scope; - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("Call").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Nan::SetPrototypeMethod(tpl, "startBatch", StartBatch); - Nan::SetPrototypeMethod(tpl, "cancel", Cancel); - Nan::SetPrototypeMethod(tpl, "cancelWithStatus", CancelWithStatus); - Nan::SetPrototypeMethod(tpl, "getPeer", GetPeer); - Nan::SetPrototypeMethod(tpl, "setCredentials", SetCredentials); - fun_tpl.Reset(tpl); - Local ctr = Nan::GetFunction(tpl).ToLocalChecked(); - Nan::Set(exports, Nan::New("Call").ToLocalChecked(), ctr); - constructor = new Callback(ctr); -} - -bool Call::HasInstance(Local val) { - HandleScope scope; - return Nan::New(fun_tpl)->HasInstance(val); -} - -Local Call::WrapStruct(grpc_call *call) { - EscapableHandleScope scope; - if (call == NULL) { - return scope.Escape(Nan::Null()); - } - const int argc = 1; - Local argv[argc] = { - Nan::New(reinterpret_cast(call))}; - MaybeLocal maybe_instance = - Nan::NewInstance(constructor->GetFunction(), argc, argv); - if (maybe_instance.IsEmpty()) { - return scope.Escape(Nan::Null()); - } else { - return scope.Escape(maybe_instance.ToLocalChecked()); - } -} - -void Call::CompleteBatch(bool is_final_op) { - if (is_final_op) { - this->has_final_op_completed = true; - } - this->pending_batches--; - if (this->has_final_op_completed && this->pending_batches == 0) { - this->DestroyCall(); - } -} - -NAN_METHOD(Call::New) { - /* Arguments: - * 0: Channel to make the call on - * 1: Method - * 2: Deadline - * 3: host - * 4: parent Call - * 5: propagation flags - */ - if (info.IsConstructCall()) { - Call *call; - if (info[0]->IsExternal()) { - Local ext = info[0].As(); - // This option is used for wrapping an existing call - grpc_call *call_value = reinterpret_cast(ext->Value()); - call = new Call(call_value); - } else { - if (!Channel::HasInstance(info[0])) { - return Nan::ThrowTypeError("Call's first argument must be a Channel"); - } - if (!info[1]->IsString()) { - return Nan::ThrowTypeError("Call's second argument must be a string"); - } - if (!(info[2]->IsNumber() || info[2]->IsDate())) { - return Nan::ThrowTypeError( - "Call's third argument must be a date or a number"); - } - // These arguments are at the end because they are optional - grpc_call *parent_call = NULL; - if (Call::HasInstance(info[4])) { - Call *parent_obj = - ObjectWrap::Unwrap(Nan::To(info[4]).ToLocalChecked()); - parent_call = parent_obj->wrapped_call; - } else if (!(info[4]->IsUndefined() || info[4]->IsNull())) { - return Nan::ThrowTypeError( - "Call's fifth argument must be another call, if provided"); - } - uint32_t propagate_flags = GRPC_PROPAGATE_DEFAULTS; - if (info[5]->IsUint32()) { - propagate_flags = Nan::To(info[5]).FromJust(); - } else if (!(info[5]->IsUndefined() || info[5]->IsNull())) { - return Nan::ThrowTypeError( - "Call's sixth argument must be propagate flags, if provided"); - } - Local channel_object = Nan::To(info[0]).ToLocalChecked(); - Channel *channel = ObjectWrap::Unwrap(channel_object); - if (channel->GetWrappedChannel() == NULL) { - return Nan::ThrowError("Call cannot be created from a closed channel"); - } - double deadline = Nan::To(info[2]).FromJust(); - grpc_channel *wrapped_channel = channel->GetWrappedChannel(); - grpc_call *wrapped_call; - grpc_slice method = - CreateSliceFromString(Nan::To(info[1]).ToLocalChecked()); - if (info[3]->IsString()) { - grpc_slice *host = new grpc_slice; - *host = - CreateSliceFromString(Nan::To(info[3]).ToLocalChecked()); - wrapped_call = grpc_channel_create_call( - wrapped_channel, parent_call, propagate_flags, GetCompletionQueue(), - method, host, MillisecondsToTimespec(deadline), NULL); - delete host; - } else if (info[3]->IsUndefined() || info[3]->IsNull()) { - wrapped_call = grpc_channel_create_call( - wrapped_channel, parent_call, propagate_flags, GetCompletionQueue(), - method, NULL, MillisecondsToTimespec(deadline), NULL); - } else { - return Nan::ThrowTypeError("Call's fourth argument must be a string"); - } - grpc_slice_unref(method); - call = new Call(wrapped_call); - Nan::Set(info.This(), Nan::New("channel_").ToLocalChecked(), - channel_object); - } - call->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - } else { - const int argc = 4; - Local argv[argc] = {info[0], info[1], info[2], info[3]}; - MaybeLocal maybe_instance = - Nan::NewInstance(constructor->GetFunction(), argc, argv); - if (maybe_instance.IsEmpty()) { - // There's probably a pending exception - return; - } else { - info.GetReturnValue().Set(maybe_instance.ToLocalChecked()); - } - } -} - -NAN_METHOD(Call::StartBatch) { - if (!Call::HasInstance(info.This())) { - return Nan::ThrowTypeError("startBatch can only be called on Call objects"); - } - if (!info[0]->IsObject()) { - return Nan::ThrowError("startBatch's first argument must be an object"); - } - if (!info[1]->IsFunction()) { - return Nan::ThrowError("startBatch's second argument must be a callback"); - } - Local callback_func = info[1].As(); - Call *call = ObjectWrap::Unwrap(info.This()); - if (call->wrapped_call == NULL) { - /* This implies that the call has completed and has been destroyed. To - * emulate - * previous behavior, we should call the callback immediately with an error, - * as though the batch had failed in core */ - Local argv[] = { - Nan::Error("The async function failed because the call has completed")}; - Nan::Call(callback_func, Nan::New(), 1, argv); - return; - } - Local obj = Nan::To(info[0]).ToLocalChecked(); - Local keys = Nan::GetOwnPropertyNames(obj).ToLocalChecked(); - size_t nops = keys->Length(); - vector ops(nops); - unique_ptr op_vector(new OpVec()); - for (unsigned int i = 0; i < nops; i++) { - unique_ptr op; - MaybeLocal maybe_key = Nan::Get(keys, i); - if (maybe_key.IsEmpty() || (!maybe_key.ToLocalChecked()->IsUint32())) { - return Nan::ThrowError( - "startBatch's first argument's keys must be integers"); - } - uint32_t type = Nan::To(maybe_key.ToLocalChecked()).FromJust(); - ops[i].op = static_cast(type); - ops[i].flags = 0; - ops[i].reserved = NULL; - switch (type) { - case GRPC_OP_SEND_INITIAL_METADATA: - op.reset(new SendMetadataOp()); - break; - case GRPC_OP_SEND_MESSAGE: - op.reset(new SendMessageOp()); - break; - case GRPC_OP_SEND_CLOSE_FROM_CLIENT: - op.reset(new SendClientCloseOp()); - break; - case GRPC_OP_SEND_STATUS_FROM_SERVER: - op.reset(new SendServerStatusOp()); - break; - case GRPC_OP_RECV_INITIAL_METADATA: - op.reset(new GetMetadataOp()); - break; - case GRPC_OP_RECV_MESSAGE: - op.reset(new ReadMessageOp()); - break; - case GRPC_OP_RECV_STATUS_ON_CLIENT: - op.reset(new ClientStatusOp()); - break; - case GRPC_OP_RECV_CLOSE_ON_SERVER: - op.reset(new ServerCloseResponseOp()); - break; - default: - return Nan::ThrowError("Argument object had an unrecognized key"); - } - if (!op->ParseOp(obj->Get(type), &ops[i])) { - return Nan::ThrowTypeError("Incorrectly typed arguments to startBatch"); - } - op_vector->push_back(std::move(op)); - } - Callback *callback = new Callback(callback_func); - grpc_call_error error = grpc_call_start_batch( - call->wrapped_call, &ops[0], nops, - new struct tag(callback, op_vector.release(), call, info.This()), NULL); - if (error != GRPC_CALL_OK) { - return Nan::ThrowError(nanErrorWithCode("startBatch failed", error)); - } - call->pending_batches++; - CompletionQueueNext(); -} - -NAN_METHOD(Call::Cancel) { - if (!Call::HasInstance(info.This())) { - return Nan::ThrowTypeError("cancel can only be called on Call objects"); - } - Call *call = ObjectWrap::Unwrap(info.This()); - if (call->wrapped_call == NULL) { - /* Cancel is supposed to be idempotent. If the call has already finished, - * cancel should just complete silently */ - return; - } - grpc_call_error error = grpc_call_cancel(call->wrapped_call, NULL); - if (error != GRPC_CALL_OK) { - return Nan::ThrowError(nanErrorWithCode("cancel failed", error)); - } -} - -NAN_METHOD(Call::CancelWithStatus) { - Nan::HandleScope scope; - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError("cancel can only be called on Call objects"); - } - if (!info[0]->IsUint32()) { - return Nan::ThrowTypeError( - "cancelWithStatus's first argument must be a status code"); - } - if (!info[1]->IsString()) { - return Nan::ThrowTypeError( - "cancelWithStatus's second argument must be a string"); - } - Call *call = ObjectWrap::Unwrap(info.This()); - if (call->wrapped_call == NULL) { - /* Cancel is supposed to be idempotent. If the call has already finished, - * cancel should just complete silently */ - return; - } - grpc_status_code code = - static_cast(Nan::To(info[0]).FromJust()); - if (code == GRPC_STATUS_OK) { - return Nan::ThrowRangeError( - "cancelWithStatus cannot be called with OK status"); - } - Utf8String details(info[1]); - grpc_call_cancel_with_status(call->wrapped_call, code, *details, NULL); -} - -NAN_METHOD(Call::GetPeer) { - Nan::HandleScope scope; - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError("getPeer can only be called on Call objects"); - } - Call *call = ObjectWrap::Unwrap(info.This()); - Local peer_value = Nan::New(call->peer).ToLocalChecked(); - info.GetReturnValue().Set(peer_value); -} - -NAN_METHOD(Call::SetCredentials) { - Nan::HandleScope scope; - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError( - "setCredentials can only be called on Call objects"); - } - if (!CallCredentials::HasInstance(info[0])) { - return Nan::ThrowTypeError( - "setCredentials' first argument must be a CallCredentials"); - } - Call *call = ObjectWrap::Unwrap(info.This()); - if (call->wrapped_call == NULL) { - return Nan::ThrowError( - "Cannot set credentials on a call that has already started"); - } - CallCredentials *creds_object = ObjectWrap::Unwrap( - Nan::To(info[0]).ToLocalChecked()); - grpc_call_credentials *creds = creds_object->GetWrappedCredentials(); - grpc_call_error error = GRPC_CALL_ERROR; - if (creds) { - error = grpc_call_set_credentials(call->wrapped_call, creds); - } - info.GetReturnValue().Set(Nan::New(error)); -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/call.h b/packages/grpc-native-core/ext/call.h deleted file mode 100644 index 50248c0bc..000000000 --- a/packages/grpc-native-core/ext/call.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef NET_GRPC_NODE_CALL_H_ -#define NET_GRPC_NODE_CALL_H_ - -#include -#include - -#include -#include -#include "grpc/grpc.h" -#include "grpc/support/log.h" - -#include "channel.h" - -namespace grpc { -namespace node { - -using std::unique_ptr; -using std::shared_ptr; - -v8::Local nanErrorWithCode(const char *msg, grpc_call_error code); - -v8::Local ParseMetadata(const grpc_metadata_array *metadata_array); - -bool CreateMetadataArray(v8::Local metadata, - grpc_metadata_array *array); - -void DestroyMetadataArray(grpc_metadata_array *array); - -/* Wrapper class for grpc_call structs. */ -class Call : public Nan::ObjectWrap { - public: - static void Init(v8::Local exports); - static bool HasInstance(v8::Local val); - /* Wrap a grpc_call struct in a javascript object */ - static v8::Local WrapStruct(grpc_call *call); - - void CompleteBatch(bool is_final_op); - - private: - explicit Call(grpc_call *call); - ~Call(); - - // Prevent copying - Call(const Call &); - Call &operator=(const Call &); - - void DestroyCall(); - - static NAN_METHOD(New); - static NAN_METHOD(StartBatch); - static NAN_METHOD(Cancel); - static NAN_METHOD(CancelWithStatus); - static NAN_METHOD(GetPeer); - static NAN_METHOD(SetCredentials); - static Nan::Callback *constructor; - // Used for typechecking instances of this javascript class - static Nan::Persistent fun_tpl; - - grpc_call *wrapped_call; - // The number of ops that were started but not completed on this call - int pending_batches; - /* Indicates whether the "final" op on a call has completed. For a client - call, this is GRPC_OP_RECV_STATUS_ON_CLIENT and for a server call, this - is GRPC_OP_SEND_STATUS_FROM_SERVER */ - bool has_final_op_completed; - char *peer; -}; - -class Op { - public: - virtual v8::Local GetNodeValue() const = 0; - virtual bool ParseOp(v8::Local value, grpc_op *out) = 0; - virtual ~Op(); - v8::Local GetOpType() const; - virtual bool IsFinalOp() = 0; - virtual void OnComplete(bool success) = 0; - - protected: - virtual std::string GetTypeString() const = 0; -}; - -typedef std::vector> OpVec; -struct tag { - tag(Nan::Callback *callback, OpVec *ops, Call *call, - v8::Local call_value); - ~tag(); - Nan::Callback *callback; - OpVec *ops; - Call *call; - Nan::Persistent> - call_persist; -}; - -void DestroyTag(void *tag); - -void CompleteTag(void *tag, const char *error_message); - -} // namespace node -} // namespace grpc - -#endif // NET_GRPC_NODE_CALL_H_ diff --git a/packages/grpc-native-core/ext/call_credentials.cc b/packages/grpc-native-core/ext/call_credentials.cc deleted file mode 100644 index 2b1cb35f2..000000000 --- a/packages/grpc-native-core/ext/call_credentials.cc +++ /dev/null @@ -1,277 +0,0 @@ -/* - * - * 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. - * - */ - -#include -#include -#include - -#include - -#include "call.h" -#include "call_credentials.h" -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" -#include "grpc/support/log.h" - -namespace grpc { -namespace node { - -using Nan::Callback; -using Nan::EscapableHandleScope; -using Nan::HandleScope; -using Nan::Maybe; -using Nan::MaybeLocal; -using Nan::ObjectWrap; -using Nan::Persistent; -using Nan::Utf8String; - -using v8::Exception; -using v8::External; -using v8::Function; -using v8::FunctionTemplate; -using v8::Integer; -using v8::Local; -using v8::Object; -using v8::ObjectTemplate; -using v8::Value; - -Nan::Callback *CallCredentials::constructor; -Persistent CallCredentials::fun_tpl; - -static Callback *plugin_callback; - -CallCredentials::CallCredentials(grpc_call_credentials *credentials) - : wrapped_credentials(credentials) {} - -CallCredentials::~CallCredentials() { - grpc_call_credentials_release(wrapped_credentials); -} - -void CallCredentials::Init(Local exports) { - HandleScope scope; - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("CallCredentials").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Nan::SetPrototypeMethod(tpl, "compose", Compose); - fun_tpl.Reset(tpl); - Local ctr = Nan::GetFunction(tpl).ToLocalChecked(); - Nan::Set(ctr, Nan::New("createFromPlugin").ToLocalChecked(), - Nan::GetFunction(Nan::New(CreateFromPlugin)) - .ToLocalChecked()); - Nan::Set(exports, Nan::New("CallCredentials").ToLocalChecked(), ctr); - constructor = new Nan::Callback(ctr); - - Local callback_tpl = - Nan::New(PluginCallback); - plugin_callback = - new Callback(Nan::GetFunction(callback_tpl).ToLocalChecked()); -} - -bool CallCredentials::HasInstance(Local val) { - HandleScope scope; - return Nan::New(fun_tpl)->HasInstance(val); -} - -Local CallCredentials::WrapStruct(grpc_call_credentials *credentials) { - EscapableHandleScope scope; - const int argc = 1; - if (credentials == NULL) { - return scope.Escape(Nan::Null()); - } - Local argv[argc] = { - Nan::New(reinterpret_cast(credentials))}; - MaybeLocal maybe_instance = - Nan::NewInstance(constructor->GetFunction(), argc, argv); - if (maybe_instance.IsEmpty()) { - return scope.Escape(Nan::Null()); - } else { - return scope.Escape(maybe_instance.ToLocalChecked()); - } -} - -grpc_call_credentials *CallCredentials::GetWrappedCredentials() { - return wrapped_credentials; -} - -NAN_METHOD(CallCredentials::New) { - if (info.IsConstructCall()) { - if (!info[0]->IsExternal()) { - return Nan::ThrowTypeError( - "CallCredentials can only be created with the provided functions"); - } - Local ext = info[0].As(); - grpc_call_credentials *creds_value = - reinterpret_cast(ext->Value()); - CallCredentials *credentials = new CallCredentials(creds_value); - credentials->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - return; - } else { - // This should never be called directly - return Nan::ThrowTypeError( - "CallCredentials can only be created with the provided functions"); - } -} - -NAN_METHOD(CallCredentials::Compose) { - if (!CallCredentials::HasInstance(info.This())) { - return Nan::ThrowTypeError( - "compose can only be called on CallCredentials objects"); - } - if (!CallCredentials::HasInstance(info[0])) { - return Nan::ThrowTypeError( - "compose's first argument must be a CallCredentials object"); - } - CallCredentials *self = ObjectWrap::Unwrap(info.This()); - CallCredentials *other = ObjectWrap::Unwrap( - Nan::To(info[0]).ToLocalChecked()); - grpc_call_credentials *creds = grpc_composite_call_credentials_create( - self->wrapped_credentials, other->wrapped_credentials, NULL); - info.GetReturnValue().Set(WrapStruct(creds)); -} - -NAN_METHOD(CallCredentials::CreateFromPlugin) { - if (!info[0]->IsFunction()) { - return Nan::ThrowTypeError( - "createFromPlugin's argument must be a function"); - } - grpc_metadata_credentials_plugin plugin; - plugin_state *state = new plugin_state; - state->callback = new Nan::Callback(info[0].As()); - state->pending_callbacks = new std::queue(); - uv_mutex_init(&state->plugin_mutex); - uv_async_init(uv_default_loop(), &state->plugin_async, SendPluginCallback); - uv_unref((uv_handle_t *)&state->plugin_async); - - state->plugin_async.data = state; - - plugin.get_metadata = plugin_get_metadata; - plugin.destroy = plugin_destroy_state; - plugin.state = reinterpret_cast(state); - plugin.type = ""; - grpc_call_credentials *creds = - grpc_metadata_credentials_create_from_plugin(plugin, NULL); - info.GetReturnValue().Set(WrapStruct(creds)); -} - -NAN_METHOD(PluginCallback) { - // Arguments: status code, error details, metadata - if (!info[0]->IsUint32()) { - return Nan::ThrowTypeError( - "The callback's first argument must be a status code"); - } - if (!info[1]->IsString()) { - return Nan::ThrowTypeError( - "The callback's second argument must be a string"); - } - if (!info[2]->IsObject()) { - return Nan::ThrowTypeError( - "The callback's third argument must be an object"); - } - if (!info[3]->IsObject()) { - return Nan::ThrowTypeError( - "The callback's fourth argument must be an object"); - } - grpc_status_code code = - static_cast(Nan::To(info[0]).FromJust()); - Utf8String details_utf8_str(info[1]); - char *details = *details_utf8_str; - grpc_metadata_array array; - grpc_metadata_array_init(&array); - Local callback_data = Nan::To(info[3]).ToLocalChecked(); - if (!CreateMetadataArray(Nan::To(info[2]).ToLocalChecked(), &array)) { - return Nan::ThrowError("Failed to parse metadata"); - } - grpc_credentials_plugin_metadata_cb cb = - reinterpret_cast( - Nan::Get(callback_data, Nan::New("cb").ToLocalChecked()) - .ToLocalChecked() - .As() - ->Value()); - void *user_data = - Nan::Get(callback_data, Nan::New("user_data").ToLocalChecked()) - .ToLocalChecked() - .As() - ->Value(); - cb(user_data, array.metadata, array.count, code, details); - DestroyMetadataArray(&array); -} - -NAUV_WORK_CB(SendPluginCallback) { - Nan::HandleScope scope; - plugin_state *state = reinterpret_cast(async->data); - std::queue callbacks; - uv_mutex_lock(&state->plugin_mutex); - state->pending_callbacks->swap(callbacks); - uv_mutex_unlock(&state->plugin_mutex); - while (!callbacks.empty()) { - plugin_callback_data *data = callbacks.front(); - callbacks.pop(); - Local callback_data = Nan::New(); - Nan::Set(callback_data, Nan::New("cb").ToLocalChecked(), - Nan::New(reinterpret_cast(data->cb))); - Nan::Set(callback_data, Nan::New("user_data").ToLocalChecked(), - Nan::New(data->user_data)); - const int argc = 3; - v8::Local argv[argc] = { - Nan::New(data->service_url).ToLocalChecked(), callback_data, - // Get Local from Nan::Callback* - **plugin_callback}; - Nan::Callback *callback = state->callback; - callback->Call(argc, argv); - delete data; - } -} - -int plugin_get_metadata( - void *state, grpc_auth_metadata_context context, - grpc_credentials_plugin_metadata_cb cb, - void *user_data, - grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX], - size_t *num_creds_md, grpc_status_code *status, - const char **error_details) { - plugin_state *p_state = reinterpret_cast(state); - plugin_callback_data *data = new plugin_callback_data; - data->service_url = context.service_url; - data->cb = cb; - data->user_data = user_data; - - uv_mutex_lock(&p_state->plugin_mutex); - p_state->pending_callbacks->push(data); - uv_mutex_unlock(&p_state->plugin_mutex); - - uv_async_send(&p_state->plugin_async); - return 0; // Async processing. -} - -void plugin_uv_close_cb(uv_handle_t *handle) { - uv_async_t *async = reinterpret_cast(handle); - plugin_state *state = reinterpret_cast(async->data); - uv_mutex_destroy(&state->plugin_mutex); - delete state->pending_callbacks; - delete state->callback; - delete state; -} - -void plugin_destroy_state(void *ptr) { - plugin_state *state = reinterpret_cast(ptr); - uv_close((uv_handle_t *)&state->plugin_async, plugin_uv_close_cb); -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/call_credentials.h b/packages/grpc-native-core/ext/call_credentials.h deleted file mode 100644 index 323eb4f26..000000000 --- a/packages/grpc-native-core/ext/call_credentials.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef GRPC_NODE_CALL_CREDENTIALS_H_ -#define GRPC_NODE_CALL_CREDENTIALS_H_ - -#include - -#include -#include -#include -#include "grpc/grpc_security.h" - -namespace grpc { -namespace node { - -class CallCredentials : public Nan::ObjectWrap { - public: - static void Init(v8::Local exports); - static bool HasInstance(v8::Local val); - /* Wrap a grpc_call_credentials struct in a javascript object */ - static v8::Local WrapStruct(grpc_call_credentials *credentials); - - /* Returns the grpc_call_credentials struct that this object wraps */ - grpc_call_credentials *GetWrappedCredentials(); - - private: - explicit CallCredentials(grpc_call_credentials *credentials); - ~CallCredentials(); - - // Prevent copying - CallCredentials(const CallCredentials &); - CallCredentials &operator=(const CallCredentials &); - - static NAN_METHOD(New); - static NAN_METHOD(CreateSsl); - static NAN_METHOD(CreateFromPlugin); - - static NAN_METHOD(Compose); - static Nan::Callback *constructor; - // Used for typechecking instances of this javascript class - static Nan::Persistent fun_tpl; - - grpc_call_credentials *wrapped_credentials; -}; - -/* Auth metadata plugin functionality */ - -typedef struct plugin_callback_data { - const char *service_url; - grpc_credentials_plugin_metadata_cb cb; - void *user_data; -} plugin_callback_data; - -typedef struct plugin_state { - Nan::Callback *callback; - std::queue *pending_callbacks; - uv_mutex_t plugin_mutex; - // async.data == this - uv_async_t plugin_async; -} plugin_state; - -int plugin_get_metadata( - void *state, grpc_auth_metadata_context context, - grpc_credentials_plugin_metadata_cb cb, - void *user_data, - grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX], - size_t *num_creds_md, grpc_status_code *status, - const char **error_details); - -void plugin_destroy_state(void *state); - -NAN_METHOD(PluginCallback); - -NAUV_WORK_CB(SendPluginCallback); - -} // namespace node -} // namepsace grpc - -#endif // GRPC_NODE_CALL_CREDENTIALS_H_ diff --git a/packages/grpc-native-core/ext/channel.cc b/packages/grpc-native-core/ext/channel.cc deleted file mode 100644 index fc085fa01..000000000 --- a/packages/grpc-native-core/ext/channel.cc +++ /dev/null @@ -1,272 +0,0 @@ -/* - * - * 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. - * - */ - -#include - -#include "grpc/support/log.h" - -#include -#include -#include "call.h" -#include "channel.h" -#include "channel_credentials.h" -#include "completion_queue.h" -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" -#include "timeval.h" - -namespace grpc { -namespace node { - -using Nan::Callback; -using Nan::EscapableHandleScope; -using Nan::HandleScope; -using Nan::Maybe; -using Nan::MaybeLocal; -using Nan::ObjectWrap; -using Nan::Persistent; -using Nan::Utf8String; - -using v8::Array; -using v8::Exception; -using v8::Function; -using v8::FunctionTemplate; -using v8::Integer; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::String; -using v8::Value; - -Callback *Channel::constructor; -Persistent Channel::fun_tpl; - -bool ParseChannelArgs(Local args_val, - grpc_channel_args **channel_args_ptr) { - if (args_val->IsUndefined() || args_val->IsNull()) { - *channel_args_ptr = NULL; - return true; - } - if (!args_val->IsObject()) { - *channel_args_ptr = NULL; - return false; - } - grpc_channel_args *channel_args = - reinterpret_cast(malloc(sizeof(grpc_channel_args))); - *channel_args_ptr = channel_args; - Local args_hash = Nan::To(args_val).ToLocalChecked(); - Local keys = Nan::GetOwnPropertyNames(args_hash).ToLocalChecked(); - channel_args->num_args = keys->Length(); - channel_args->args = reinterpret_cast( - calloc(channel_args->num_args, sizeof(grpc_arg))); - for (unsigned int i = 0; i < channel_args->num_args; i++) { - Local key = Nan::Get(keys, i).ToLocalChecked(); - Utf8String key_str(key); - if (*key_str == NULL) { - // Key string onversion failed - return false; - } - Local value = Nan::Get(args_hash, key).ToLocalChecked(); - if (value->IsInt32()) { - channel_args->args[i].type = GRPC_ARG_INTEGER; - channel_args->args[i].value.integer = Nan::To(value).FromJust(); - } else if (value->IsString()) { - Utf8String val_str(value); - channel_args->args[i].type = GRPC_ARG_STRING; - channel_args->args[i].value.string = - reinterpret_cast(calloc(val_str.length() + 1, sizeof(char))); - memcpy(channel_args->args[i].value.string, *val_str, - val_str.length() + 1); - } else { - // The value does not match either of the accepted types - return false; - } - channel_args->args[i].key = - reinterpret_cast(calloc(key_str.length() + 1, sizeof(char))); - memcpy(channel_args->args[i].key, *key_str, key_str.length() + 1); - } - return true; -} - -void DeallocateChannelArgs(grpc_channel_args *channel_args) { - if (channel_args == NULL) { - return; - } - for (size_t i = 0; i < channel_args->num_args; i++) { - if (channel_args->args[i].key == NULL) { - /* NULL key implies that this argument and all subsequent arguments failed - * to parse */ - break; - } - free(channel_args->args[i].key); - if (channel_args->args[i].type == GRPC_ARG_STRING) { - free(channel_args->args[i].value.string); - } - } - free(channel_args->args); - free(channel_args); -} - -Channel::Channel(grpc_channel *channel) : wrapped_channel(channel) {} - -Channel::~Channel() { - gpr_log(GPR_DEBUG, "Destroying channel"); - if (wrapped_channel != NULL) { - grpc_channel_destroy(wrapped_channel); - } -} - -void Channel::Init(Local exports) { - Nan::HandleScope scope; - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("Channel").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Nan::SetPrototypeMethod(tpl, "close", Close); - Nan::SetPrototypeMethod(tpl, "getTarget", GetTarget); - Nan::SetPrototypeMethod(tpl, "getConnectivityState", GetConnectivityState); - Nan::SetPrototypeMethod(tpl, "watchConnectivityState", - WatchConnectivityState); - fun_tpl.Reset(tpl); - Local ctr = Nan::GetFunction(tpl).ToLocalChecked(); - Nan::Set(exports, Nan::New("Channel").ToLocalChecked(), ctr); - constructor = new Callback(ctr); -} - -bool Channel::HasInstance(Local val) { - HandleScope scope; - return Nan::New(fun_tpl)->HasInstance(val); -} - -grpc_channel *Channel::GetWrappedChannel() { return this->wrapped_channel; } - -NAN_METHOD(Channel::New) { - if (info.IsConstructCall()) { - if (!info[0]->IsString()) { - return Nan::ThrowTypeError( - "Channel expects a string, a credential and an object"); - } - grpc_channel *wrapped_channel; - // Owned by the Channel object - Utf8String host(info[0]); - grpc_channel_credentials *creds; - if (!ChannelCredentials::HasInstance(info[1])) { - return Nan::ThrowTypeError( - "Channel's second argument must be a ChannelCredentials"); - } - ChannelCredentials *creds_object = ObjectWrap::Unwrap( - Nan::To(info[1]).ToLocalChecked()); - creds = creds_object->GetWrappedCredentials(); - grpc_channel_args *channel_args_ptr = NULL; - if (!ParseChannelArgs(info[2], &channel_args_ptr)) { - DeallocateChannelArgs(channel_args_ptr); - return Nan::ThrowTypeError( - "Channel options must be an object with " - "string keys and integer or string values"); - } - if (creds == NULL) { - wrapped_channel = - grpc_insecure_channel_create(*host, channel_args_ptr, NULL); - } else { - wrapped_channel = - grpc_secure_channel_create(creds, *host, channel_args_ptr, NULL); - } - DeallocateChannelArgs(channel_args_ptr); - Channel *channel = new Channel(wrapped_channel); - channel->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - return; - } else { - const int argc = 3; - Local argv[argc] = {info[0], info[1], info[2]}; - MaybeLocal maybe_instance = - Nan::NewInstance(constructor->GetFunction(), argc, argv); - if (maybe_instance.IsEmpty()) { - // There's probably a pending exception - return; - } else { - info.GetReturnValue().Set(maybe_instance.ToLocalChecked()); - } - } -} - -NAN_METHOD(Channel::Close) { - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError("close can only be called on Channel objects"); - } - Channel *channel = ObjectWrap::Unwrap(info.This()); - if (channel->wrapped_channel != NULL) { - grpc_channel_destroy(channel->wrapped_channel); - channel->wrapped_channel = NULL; - } -} - -NAN_METHOD(Channel::GetTarget) { - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError( - "getTarget can only be called on Channel objects"); - } - Channel *channel = ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set( - Nan::New(grpc_channel_get_target(channel->wrapped_channel)) - .ToLocalChecked()); -} - -NAN_METHOD(Channel::GetConnectivityState) { - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError( - "getConnectivityState can only be called on Channel objects"); - } - Channel *channel = ObjectWrap::Unwrap(info.This()); - int try_to_connect = (int)info[0]->Equals(Nan::True()); - info.GetReturnValue().Set(grpc_channel_check_connectivity_state( - channel->wrapped_channel, try_to_connect)); -} - -NAN_METHOD(Channel::WatchConnectivityState) { - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError( - "watchConnectivityState can only be called on Channel objects"); - } - if (!info[0]->IsUint32()) { - return Nan::ThrowTypeError( - "watchConnectivityState's first argument must be a channel state"); - } - if (!(info[1]->IsNumber() || info[1]->IsDate())) { - return Nan::ThrowTypeError( - "watchConnectivityState's second argument must be a date or a number"); - } - if (!info[2]->IsFunction()) { - return Nan::ThrowTypeError( - "watchConnectivityState's third argument must be a callback"); - } - grpc_connectivity_state last_state = static_cast( - Nan::To(info[0]).FromJust()); - double deadline = Nan::To(info[1]).FromJust(); - Local callback_func = info[2].As(); - Nan::Callback *callback = new Callback(callback_func); - Channel *channel = ObjectWrap::Unwrap(info.This()); - unique_ptr ops(new OpVec()); - grpc_channel_watch_connectivity_state( - channel->wrapped_channel, last_state, MillisecondsToTimespec(deadline), - GetCompletionQueue(), - new struct tag(callback, ops.release(), NULL, Nan::Null())); - CompletionQueueNext(); -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/channel.h b/packages/grpc-native-core/ext/channel.h deleted file mode 100644 index a93dbe9d2..000000000 --- a/packages/grpc-native-core/ext/channel.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef NET_GRPC_NODE_CHANNEL_H_ -#define NET_GRPC_NODE_CHANNEL_H_ - -#include -#include -#include "grpc/grpc.h" - -namespace grpc { -namespace node { - -bool ParseChannelArgs(v8::Local args_val, - grpc_channel_args **channel_args_ptr); - -void DeallocateChannelArgs(grpc_channel_args *channel_args); - -/* Wrapper class for grpc_channel structs */ -class Channel : public Nan::ObjectWrap { - public: - static void Init(v8::Local exports); - static bool HasInstance(v8::Local val); - /* This is used to typecheck javascript objects before converting them to - this type */ - static v8::Persistent prototype; - - /* Returns the grpc_channel struct that this object wraps */ - grpc_channel *GetWrappedChannel(); - - private: - explicit Channel(grpc_channel *channel); - ~Channel(); - - // Prevent copying - Channel(const Channel &); - Channel &operator=(const Channel &); - - static NAN_METHOD(New); - static NAN_METHOD(Close); - static NAN_METHOD(GetTarget); - static NAN_METHOD(GetConnectivityState); - static NAN_METHOD(WatchConnectivityState); - static Nan::Callback *constructor; - static Nan::Persistent fun_tpl; - - grpc_channel *wrapped_channel; -}; - -} // namespace node -} // namespace grpc - -#endif // NET_GRPC_NODE_CHANNEL_H_ diff --git a/packages/grpc-native-core/ext/channel_credentials.cc b/packages/grpc-native-core/ext/channel_credentials.cc deleted file mode 100644 index 9e87fb61b..000000000 --- a/packages/grpc-native-core/ext/channel_credentials.cc +++ /dev/null @@ -1,188 +0,0 @@ -/* - * - * 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. - * - */ - -#include - -#include "call.h" -#include "call_credentials.h" -#include "channel_credentials.h" -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" -#include "grpc/support/log.h" - -namespace grpc { -namespace node { - -using Nan::Callback; -using Nan::EscapableHandleScope; -using Nan::HandleScope; -using Nan::Maybe; -using Nan::MaybeLocal; -using Nan::ObjectWrap; -using Nan::Persistent; -using Nan::Utf8String; - -using v8::Exception; -using v8::External; -using v8::Function; -using v8::FunctionTemplate; -using v8::Integer; -using v8::Local; -using v8::Object; -using v8::ObjectTemplate; -using v8::Value; - -Nan::Callback *ChannelCredentials::constructor; -Persistent ChannelCredentials::fun_tpl; - -ChannelCredentials::ChannelCredentials(grpc_channel_credentials *credentials) - : wrapped_credentials(credentials) {} - -ChannelCredentials::~ChannelCredentials() { - grpc_channel_credentials_release(wrapped_credentials); -} - -void ChannelCredentials::Init(Local exports) { - HandleScope scope; - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("ChannelCredentials").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Nan::SetPrototypeMethod(tpl, "compose", Compose); - fun_tpl.Reset(tpl); - Local ctr = Nan::GetFunction(tpl).ToLocalChecked(); - Nan::Set( - ctr, Nan::New("createSsl").ToLocalChecked(), - Nan::GetFunction(Nan::New(CreateSsl)).ToLocalChecked()); - Nan::Set(ctr, Nan::New("createInsecure").ToLocalChecked(), - Nan::GetFunction(Nan::New(CreateInsecure)) - .ToLocalChecked()); - Nan::Set(exports, Nan::New("ChannelCredentials").ToLocalChecked(), ctr); - constructor = new Nan::Callback(ctr); -} - -bool ChannelCredentials::HasInstance(Local val) { - HandleScope scope; - return Nan::New(fun_tpl)->HasInstance(val); -} - -Local ChannelCredentials::WrapStruct( - grpc_channel_credentials *credentials) { - EscapableHandleScope scope; - const int argc = 1; - Local argv[argc] = { - Nan::New(reinterpret_cast(credentials))}; - MaybeLocal maybe_instance = - Nan::NewInstance(constructor->GetFunction(), argc, argv); - if (maybe_instance.IsEmpty()) { - return scope.Escape(Nan::Null()); - } else { - return scope.Escape(maybe_instance.ToLocalChecked()); - } -} - -grpc_channel_credentials *ChannelCredentials::GetWrappedCredentials() { - return wrapped_credentials; -} - -NAN_METHOD(ChannelCredentials::New) { - if (info.IsConstructCall()) { - if (!info[0]->IsExternal()) { - return Nan::ThrowTypeError( - "ChannelCredentials can only be created with the provided functions"); - } - Local ext = info[0].As(); - grpc_channel_credentials *creds_value = - reinterpret_cast(ext->Value()); - ChannelCredentials *credentials = new ChannelCredentials(creds_value); - credentials->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - return; - } else { - // This should never be called directly - return Nan::ThrowTypeError( - "ChannelCredentials can only be created with the provided functions"); - } -} - -NAN_METHOD(ChannelCredentials::CreateSsl) { - char *root_certs = NULL; - grpc_ssl_pem_key_cert_pair key_cert_pair = {NULL, NULL}; - if (::node::Buffer::HasInstance(info[0])) { - root_certs = ::node::Buffer::Data(info[0]); - } else if (!(info[0]->IsNull() || info[0]->IsUndefined())) { - return Nan::ThrowTypeError("createSsl's first argument must be a Buffer"); - } - if (::node::Buffer::HasInstance(info[1])) { - key_cert_pair.private_key = ::node::Buffer::Data(info[1]); - } else if (!(info[1]->IsNull() || info[1]->IsUndefined())) { - return Nan::ThrowTypeError( - "createSSl's second argument must be a Buffer if provided"); - } - if (::node::Buffer::HasInstance(info[2])) { - key_cert_pair.cert_chain = ::node::Buffer::Data(info[2]); - } else if (!(info[2]->IsNull() || info[2]->IsUndefined())) { - return Nan::ThrowTypeError( - "createSSl's third argument must be a Buffer if provided"); - } - if ((key_cert_pair.private_key == NULL) != - (key_cert_pair.cert_chain == NULL)) { - return Nan::ThrowError( - "createSsl's second and third arguments must be" - " provided or omitted together"); - } - grpc_channel_credentials *creds = grpc_ssl_credentials_create( - root_certs, key_cert_pair.private_key == NULL ? NULL : &key_cert_pair, - NULL); - if (creds == NULL) { - info.GetReturnValue().SetNull(); - } else { - info.GetReturnValue().Set(WrapStruct(creds)); - } -} - -NAN_METHOD(ChannelCredentials::Compose) { - if (!ChannelCredentials::HasInstance(info.This())) { - return Nan::ThrowTypeError( - "compose can only be called on ChannelCredentials objects"); - } - if (!CallCredentials::HasInstance(info[0])) { - return Nan::ThrowTypeError( - "compose's first argument must be a CallCredentials object"); - } - ChannelCredentials *self = - ObjectWrap::Unwrap(info.This()); - if (self->wrapped_credentials == NULL) { - return Nan::ThrowTypeError("Cannot compose insecure credential"); - } - CallCredentials *other = ObjectWrap::Unwrap( - Nan::To(info[0]).ToLocalChecked()); - grpc_channel_credentials *creds = grpc_composite_channel_credentials_create( - self->wrapped_credentials, other->GetWrappedCredentials(), NULL); - if (creds == NULL) { - info.GetReturnValue().SetNull(); - } else { - info.GetReturnValue().Set(WrapStruct(creds)); - } -} - -NAN_METHOD(ChannelCredentials::CreateInsecure) { - info.GetReturnValue().Set(WrapStruct(NULL)); -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/channel_credentials.h b/packages/grpc-native-core/ext/channel_credentials.h deleted file mode 100644 index 18c14837c..000000000 --- a/packages/grpc-native-core/ext/channel_credentials.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_ -#define NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_ - -#include -#include -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" - -namespace grpc { -namespace node { - -/* Wrapper class for grpc_channel_credentials structs */ -class ChannelCredentials : public Nan::ObjectWrap { - public: - static void Init(v8::Local exports); - static bool HasInstance(v8::Local val); - /* Wrap a grpc_channel_credentials struct in a javascript object */ - static v8::Local WrapStruct(grpc_channel_credentials *credentials); - - /* Returns the grpc_channel_credentials struct that this object wraps */ - grpc_channel_credentials *GetWrappedCredentials(); - - private: - explicit ChannelCredentials(grpc_channel_credentials *credentials); - ~ChannelCredentials(); - - // Prevent copying - ChannelCredentials(const ChannelCredentials &); - ChannelCredentials &operator=(const ChannelCredentials &); - - static NAN_METHOD(New); - static NAN_METHOD(CreateSsl); - static NAN_METHOD(CreateInsecure); - - static NAN_METHOD(Compose); - static Nan::Callback *constructor; - // Used for typechecking instances of this javascript class - static Nan::Persistent fun_tpl; - - grpc_channel_credentials *wrapped_credentials; -}; - -} // namespace node -} // namespace grpc - -#endif // NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_ diff --git a/packages/grpc-native-core/ext/completion_queue.cc b/packages/grpc-native-core/ext/completion_queue.cc deleted file mode 100644 index 0cee1a754..000000000 --- a/packages/grpc-native-core/ext/completion_queue.cc +++ /dev/null @@ -1,90 +0,0 @@ -/* - * - * Copyright 2016 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. - * - */ - -#include -#include -#include -#include - -#include "call.h" -#include "completion_queue.h" - -namespace grpc { -namespace node { - -using v8::Local; -using v8::Object; -using v8::Value; - -grpc_completion_queue *queue; -uv_prepare_t prepare; -int pending_batches; - -static void drain_completion_queue(uv_prepare_t *handle) { - Nan::HandleScope scope; - grpc_event event; - (void)handle; - do { - event = grpc_completion_queue_next(queue, gpr_inf_past(GPR_CLOCK_MONOTONIC), - NULL); - - if (event.type == GRPC_OP_COMPLETE) { - const char *error_message; - if (event.success) { - error_message = NULL; - } else { - error_message = "The async function encountered an error"; - } - CompleteTag(event.tag, error_message); - grpc::node::DestroyTag(event.tag); - pending_batches--; - } - if (pending_batches == 0) { - uv_prepare_stop(&prepare); - } - } while (event.type != GRPC_QUEUE_TIMEOUT); -} - -grpc_completion_queue *GetCompletionQueue() { return queue; } - -void CompletionQueueNext() { - if (pending_batches == 0) { - GPR_ASSERT(!uv_is_active((uv_handle_t *)&prepare)); - uv_prepare_start(&prepare, drain_completion_queue); - } - pending_batches++; -} - -void CompletionQueueInit(Local exports) { - queue = grpc_completion_queue_create_for_next(NULL); - uv_prepare_init(uv_default_loop(), &prepare); - pending_batches = 0; -} - -void CompletionQueueForcePoll() { - /* This sets the prepare object to poll on the completion queue the next time - * Node polls for IO. But it doesn't increment the number of pending batches, - * so it will immediately stop polling after that unless there is an - * intervening CompletionQueueNext call */ - if (pending_batches == 0) { - uv_prepare_start(&prepare, drain_completion_queue); - } -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/node_grpc.cc b/packages/grpc-native-core/ext/node_grpc.cc deleted file mode 100644 index a0652c282..000000000 --- a/packages/grpc-native-core/ext/node_grpc.cc +++ /dev/null @@ -1,318 +0,0 @@ -/* - * - * 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. - * - */ - -#include - -#include -#include -#include -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" -#include "grpc/support/alloc.h" -#include "grpc/support/log.h" -#include "grpc/support/time.h" - -// TODO(murgatroid99): Remove this when the endpoint API becomes public -extern "C" { -#include "src/core/lib/iomgr/pollset_uv.h" -} - -#include "call.h" -#include "call_credentials.h" -#include "channel.h" -#include "channel_credentials.h" -#include "completion_queue.h" -#include "server.h" -#include "server_credentials.h" -#include "slice.h" -#include "timeval.h" - -using grpc::node::CreateSliceFromString; - -using v8::FunctionTemplate; -using v8::Local; -using v8::Value; -using v8::Number; -using v8::Object; -using v8::Uint32; -using v8::String; - -typedef struct log_args { - gpr_log_func_args core_args; - gpr_timespec timestamp; -} log_args; - -typedef struct logger_state { - Nan::Callback *callback; - std::queue *pending_args; - uv_mutex_t mutex; - uv_async_t async; - // Indicates that a logger has been set - bool logger_set; -} logger_state; - -logger_state grpc_logger_state; - -static char *pem_root_certs = NULL; - -void InitOpTypeConstants(Local exports) { - Nan::HandleScope scope; - Local op_type = Nan::New(); - Nan::Set(exports, Nan::New("opType").ToLocalChecked(), op_type); - Local SEND_INITIAL_METADATA( - Nan::New(GRPC_OP_SEND_INITIAL_METADATA)); - Nan::Set(op_type, Nan::New("SEND_INITIAL_METADATA").ToLocalChecked(), - SEND_INITIAL_METADATA); - Local SEND_MESSAGE(Nan::New(GRPC_OP_SEND_MESSAGE)); - Nan::Set(op_type, Nan::New("SEND_MESSAGE").ToLocalChecked(), SEND_MESSAGE); - Local SEND_CLOSE_FROM_CLIENT( - Nan::New(GRPC_OP_SEND_CLOSE_FROM_CLIENT)); - Nan::Set(op_type, Nan::New("SEND_CLOSE_FROM_CLIENT").ToLocalChecked(), - SEND_CLOSE_FROM_CLIENT); - Local SEND_STATUS_FROM_SERVER( - Nan::New(GRPC_OP_SEND_STATUS_FROM_SERVER)); - Nan::Set(op_type, Nan::New("SEND_STATUS_FROM_SERVER").ToLocalChecked(), - SEND_STATUS_FROM_SERVER); - Local RECV_INITIAL_METADATA( - Nan::New(GRPC_OP_RECV_INITIAL_METADATA)); - Nan::Set(op_type, Nan::New("RECV_INITIAL_METADATA").ToLocalChecked(), - RECV_INITIAL_METADATA); - Local RECV_MESSAGE(Nan::New(GRPC_OP_RECV_MESSAGE)); - Nan::Set(op_type, Nan::New("RECV_MESSAGE").ToLocalChecked(), RECV_MESSAGE); - Local RECV_STATUS_ON_CLIENT( - Nan::New(GRPC_OP_RECV_STATUS_ON_CLIENT)); - Nan::Set(op_type, Nan::New("RECV_STATUS_ON_CLIENT").ToLocalChecked(), - RECV_STATUS_ON_CLIENT); - Local RECV_CLOSE_ON_SERVER( - Nan::New(GRPC_OP_RECV_CLOSE_ON_SERVER)); - Nan::Set(op_type, Nan::New("RECV_CLOSE_ON_SERVER").ToLocalChecked(), - RECV_CLOSE_ON_SERVER); -} - -void InitConnectivityStateConstants(Local exports) { - Nan::HandleScope scope; - Local channel_state = Nan::New(); - Nan::Set(exports, Nan::New("connectivityState").ToLocalChecked(), - channel_state); - Local IDLE(Nan::New(GRPC_CHANNEL_IDLE)); - Nan::Set(channel_state, Nan::New("IDLE").ToLocalChecked(), IDLE); - Local CONNECTING(Nan::New(GRPC_CHANNEL_CONNECTING)); - Nan::Set(channel_state, Nan::New("CONNECTING").ToLocalChecked(), CONNECTING); - Local READY(Nan::New(GRPC_CHANNEL_READY)); - Nan::Set(channel_state, Nan::New("READY").ToLocalChecked(), READY); - Local TRANSIENT_FAILURE( - Nan::New(GRPC_CHANNEL_TRANSIENT_FAILURE)); - Nan::Set(channel_state, Nan::New("TRANSIENT_FAILURE").ToLocalChecked(), - TRANSIENT_FAILURE); - Local FATAL_FAILURE(Nan::New(GRPC_CHANNEL_SHUTDOWN)); - Nan::Set(channel_state, Nan::New("FATAL_FAILURE").ToLocalChecked(), - FATAL_FAILURE); -} - -NAN_METHOD(MetadataKeyIsLegal) { - if (!info[0]->IsString()) { - return Nan::ThrowTypeError("headerKeyIsLegal's argument must be a string"); - } - Local key = Nan::To(info[0]).ToLocalChecked(); - grpc_slice slice = CreateSliceFromString(key); - info.GetReturnValue().Set(static_cast(grpc_header_key_is_legal(slice))); - grpc_slice_unref(slice); -} - -NAN_METHOD(MetadataNonbinValueIsLegal) { - if (!info[0]->IsString()) { - return Nan::ThrowTypeError( - "metadataNonbinValueIsLegal's argument must be a string"); - } - Local value = Nan::To(info[0]).ToLocalChecked(); - grpc_slice slice = CreateSliceFromString(value); - info.GetReturnValue().Set( - static_cast(grpc_header_nonbin_value_is_legal(slice))); - grpc_slice_unref(slice); -} - -NAN_METHOD(MetadataKeyIsBinary) { - if (!info[0]->IsString()) { - return Nan::ThrowTypeError( - "metadataKeyIsLegal's argument must be a string"); - } - Local key = Nan::To(info[0]).ToLocalChecked(); - grpc_slice slice = CreateSliceFromString(key); - info.GetReturnValue().Set(static_cast(grpc_is_binary_header(slice))); - grpc_slice_unref(slice); -} - -static grpc_ssl_roots_override_result get_ssl_roots_override( - char **pem_root_certs_ptr) { - *pem_root_certs_ptr = pem_root_certs; - if (pem_root_certs == NULL) { - return GRPC_SSL_ROOTS_OVERRIDE_FAIL; - } else { - return GRPC_SSL_ROOTS_OVERRIDE_OK; - } -} - -/* This should only be called once, and only before creating any - *ServerCredentials */ -NAN_METHOD(SetDefaultRootsPem) { - if (!info[0]->IsString()) { - return Nan::ThrowTypeError( - "setDefaultRootsPem's argument must be a string"); - } - Nan::Utf8String utf8_roots(info[0]); - size_t length = static_cast(utf8_roots.length()); - if (length > 0) { - const char *data = *utf8_roots; - pem_root_certs = (char *)gpr_malloc((length + 1) * sizeof(char)); - memcpy(pem_root_certs, data, length + 1); - } -} - -NAUV_WORK_CB(LogMessagesCallback) { - Nan::HandleScope scope; - std::queue args; - uv_mutex_lock(&grpc_logger_state.mutex); - grpc_logger_state.pending_args->swap(args); - uv_mutex_unlock(&grpc_logger_state.mutex); - /* Call the callback with each log message */ - while (!args.empty()) { - log_args *arg = args.front(); - args.pop(); - Local file = Nan::New(arg->core_args.file).ToLocalChecked(); - Local line = Nan::New(arg->core_args.line); - Local severity = - Nan::New(gpr_log_severity_string(arg->core_args.severity)) - .ToLocalChecked(); - Local message = Nan::New(arg->core_args.message).ToLocalChecked(); - Local timestamp = - Nan::New(grpc::node::TimespecToMilliseconds(arg->timestamp)) - .ToLocalChecked(); - const int argc = 5; - Local argv[argc] = {file, line, severity, message, timestamp}; - grpc_logger_state.callback->Call(argc, argv); - delete[] arg->core_args.message; - delete arg; - } -} - -void node_log_func(gpr_log_func_args *args) { - // TODO(mlumish): Use the core's log formatter when it becomes available - log_args *args_copy = new log_args; - size_t message_len = strlen(args->message) + 1; - char *message = new char[message_len]; - memcpy(message, args->message, message_len); - memcpy(&args_copy->core_args, args, sizeof(gpr_log_func_args)); - args_copy->core_args.message = message; - args_copy->timestamp = gpr_now(GPR_CLOCK_REALTIME); - - uv_mutex_lock(&grpc_logger_state.mutex); - grpc_logger_state.pending_args->push(args_copy); - uv_mutex_unlock(&grpc_logger_state.mutex); - - uv_async_send(&grpc_logger_state.async); -} - -void init_logger() { - memset(&grpc_logger_state, 0, sizeof(logger_state)); - grpc_logger_state.pending_args = new std::queue(); - uv_mutex_init(&grpc_logger_state.mutex); - uv_async_init(uv_default_loop(), &grpc_logger_state.async, - LogMessagesCallback); - uv_unref((uv_handle_t *)&grpc_logger_state.async); - grpc_logger_state.logger_set = false; - - gpr_log_verbosity_init(); -} - -/* This registers a JavaScript logger for messages from the gRPC core. Because - that handler has to be run in the context of the JavaScript event loop, it - will be run asynchronously. To minimize the problems that could cause for - debugging, we leave core to do its default synchronous logging until a - JavaScript logger is set */ -NAN_METHOD(SetDefaultLoggerCallback) { - if (!info[0]->IsFunction()) { - return Nan::ThrowTypeError( - "setDefaultLoggerCallback's argument must be a function"); - } - if (!grpc_logger_state.logger_set) { - gpr_set_log_function(node_log_func); - grpc_logger_state.logger_set = true; - } - grpc_logger_state.callback = new Nan::Callback(info[0].As()); -} - -NAN_METHOD(SetLogVerbosity) { - if (!info[0]->IsUint32()) { - return Nan::ThrowTypeError("setLogVerbosity's argument must be a number"); - } - gpr_log_severity severity = - static_cast(Nan::To(info[0]).FromJust()); - gpr_set_log_verbosity(severity); -} - -NAN_METHOD(ForcePoll) { - grpc::node::CompletionQueueForcePoll(); -} - -void init(Local exports) { - Nan::HandleScope scope; - grpc_init(); - grpc_set_ssl_roots_override_callback(get_ssl_roots_override); - init_logger(); - - InitOpTypeConstants(exports); - InitConnectivityStateConstants(exports); - - grpc_pollset_work_run_loop = 0; - - grpc::node::Call::Init(exports); - grpc::node::CallCredentials::Init(exports); - grpc::node::Channel::Init(exports); - grpc::node::ChannelCredentials::Init(exports); - grpc::node::Server::Init(exports); - grpc::node::ServerCredentials::Init(exports); - - grpc::node::CompletionQueueInit(exports); - - // Attach a few utility functions directly to the module - Nan::Set(exports, Nan::New("metadataKeyIsLegal").ToLocalChecked(), - Nan::GetFunction(Nan::New(MetadataKeyIsLegal)) - .ToLocalChecked()); - Nan::Set( - exports, Nan::New("metadataNonbinValueIsLegal").ToLocalChecked(), - Nan::GetFunction(Nan::New(MetadataNonbinValueIsLegal)) - .ToLocalChecked()); - Nan::Set(exports, Nan::New("metadataKeyIsBinary").ToLocalChecked(), - Nan::GetFunction(Nan::New(MetadataKeyIsBinary)) - .ToLocalChecked()); - Nan::Set(exports, Nan::New("setDefaultRootsPem").ToLocalChecked(), - Nan::GetFunction(Nan::New(SetDefaultRootsPem)) - .ToLocalChecked()); - Nan::Set( - exports, Nan::New("setDefaultLoggerCallback").ToLocalChecked(), - Nan::GetFunction(Nan::New(SetDefaultLoggerCallback)) - .ToLocalChecked()); - Nan::Set(exports, Nan::New("setLogVerbosity").ToLocalChecked(), - Nan::GetFunction(Nan::New(SetLogVerbosity)) - .ToLocalChecked()); - Nan::Set(exports, Nan::New("forcePoll").ToLocalChecked(), - Nan::GetFunction(Nan::New(ForcePoll)) - .ToLocalChecked()); -} - -NODE_MODULE(grpc_node, init) diff --git a/packages/grpc-native-core/ext/server.cc b/packages/grpc-native-core/ext/server.cc deleted file mode 100644 index c6ab5e189..000000000 --- a/packages/grpc-native-core/ext/server.cc +++ /dev/null @@ -1,342 +0,0 @@ -/* - * - * 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. - * - */ - -#include - -#include "server.h" - -#include -#include - -#include -#include "call.h" -#include "completion_queue.h" -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" -#include "grpc/support/log.h" -#include "server_credentials.h" -#include "slice.h" -#include "timeval.h" - -namespace grpc { -namespace node { - -using Nan::Callback; -using Nan::EscapableHandleScope; -using Nan::HandleScope; -using Nan::Maybe; -using Nan::MaybeLocal; -using Nan::ObjectWrap; -using Nan::Persistent; -using Nan::Utf8String; - -using std::unique_ptr; -using v8::Array; -using v8::Boolean; -using v8::Date; -using v8::Exception; -using v8::External; -using v8::Function; -using v8::FunctionTemplate; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::String; -using v8::Value; - -Nan::Callback *Server::constructor; -Persistent Server::fun_tpl; - -static Callback *shutdown_callback = NULL; - -class ServerShutdownOp : public Op { - public: - ServerShutdownOp(grpc_server *server) : server(server) {} - - ~ServerShutdownOp() {} - - Local GetNodeValue() const { return Nan::Null(); } - - bool ParseOp(Local value, grpc_op *out) { return true; } - bool IsFinalOp() { return false; } - void OnComplete(bool success) { - /* Because cancel_all_calls was called, we assume that shutdown_and_notify - completes successfully */ - grpc_server_destroy(server); - } - - grpc_server *server; - - protected: - std::string GetTypeString() const { return "shutdown"; } -}; - -class NewCallOp : public Op { - public: - NewCallOp() { - call = NULL; - grpc_call_details_init(&details); - grpc_metadata_array_init(&request_metadata); - } - - ~NewCallOp() { - grpc_call_details_destroy(&details); - grpc_metadata_array_destroy(&request_metadata); - } - - Local GetNodeValue() const { - Nan::EscapableHandleScope scope; - if (call == NULL) { - return scope.Escape(Nan::Null()); - } - Local obj = Nan::New(); - Nan::Set(obj, Nan::New("call").ToLocalChecked(), Call::WrapStruct(call)); - // TODO(murgatroid99): Use zero-copy string construction instead - Nan::Set(obj, Nan::New("method").ToLocalChecked(), - CopyStringFromSlice(details.method)); - Nan::Set(obj, Nan::New("host").ToLocalChecked(), - CopyStringFromSlice(details.host)); - Nan::Set(obj, Nan::New("deadline").ToLocalChecked(), - Nan::New(TimespecToMilliseconds(details.deadline)) - .ToLocalChecked()); - Nan::Set(obj, Nan::New("metadata").ToLocalChecked(), - ParseMetadata(&request_metadata)); - return scope.Escape(obj); - } - - bool ParseOp(Local value, grpc_op *out) { return true; } - bool IsFinalOp() { return false; } - void OnComplete(bool success) {} - - grpc_call *call; - grpc_call_details details; - grpc_metadata_array request_metadata; - - protected: - std::string GetTypeString() const { return "new_call"; } -}; - -class TryShutdownOp : public Op { - public: - TryShutdownOp(Server *server, Local server_value) : server(server) { - server_persist.Reset(server_value); - } - Local GetNodeValue() const { - EscapableHandleScope scope; - return scope.Escape(Nan::New(server_persist)); - } - bool ParseOp(Local value, grpc_op *out) { return true; } - bool IsFinalOp() { return false; } - void OnComplete(bool success) { - if (success) { - server->DestroyWrappedServer(); - } - } - - protected: - std::string GetTypeString() const { return "try_shutdown"; } - - private: - Server *server; - Nan::Persistent> - server_persist; -}; - -Server::Server(grpc_server *server) : wrapped_server(server) {} - -Server::~Server() { this->ShutdownServer(); } - -void Server::Init(Local exports) { - HandleScope scope; - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("Server").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Nan::SetPrototypeMethod(tpl, "requestCall", RequestCall); - Nan::SetPrototypeMethod(tpl, "addHttp2Port", AddHttp2Port); - Nan::SetPrototypeMethod(tpl, "start", Start); - Nan::SetPrototypeMethod(tpl, "tryShutdown", TryShutdown); - Nan::SetPrototypeMethod(tpl, "forceShutdown", ForceShutdown); - fun_tpl.Reset(tpl); - Local ctr = Nan::GetFunction(tpl).ToLocalChecked(); - Nan::Set(exports, Nan::New("Server").ToLocalChecked(), ctr); - constructor = new Callback(ctr); -} - -bool Server::HasInstance(Local val) { - HandleScope scope; - return Nan::New(fun_tpl)->HasInstance(val); -} - -void Server::DestroyWrappedServer() { - if (this->wrapped_server != NULL) { - grpc_server_destroy(this->wrapped_server); - this->wrapped_server = NULL; - } -} - -NAN_METHOD(ServerShutdownCallback) { - if (!info[0]->IsNull()) { - return Nan::ThrowError("forceShutdown failed somehow"); - } -} - -void Server::ShutdownServer() { - Nan::HandleScope scope; - if (this->wrapped_server != NULL) { - if (shutdown_callback == NULL) { - Local callback_tpl = - Nan::New(ServerShutdownCallback); - shutdown_callback = - new Callback(Nan::GetFunction(callback_tpl).ToLocalChecked()); - } - - ServerShutdownOp *op = new ServerShutdownOp(this->wrapped_server); - unique_ptr ops(new OpVec()); - ops->push_back(unique_ptr(op)); - - grpc_server_shutdown_and_notify( - this->wrapped_server, GetCompletionQueue(), - new struct tag(new Callback(**shutdown_callback), ops.release(), NULL, - Nan::Null())); - grpc_server_cancel_all_calls(this->wrapped_server); - CompletionQueueNext(); - this->wrapped_server = NULL; - } -} - -NAN_METHOD(Server::New) { - /* If this is not a constructor call, make a constructor call and return - the result */ - if (!info.IsConstructCall()) { - const int argc = 1; - Local argv[argc] = {info[0]}; - MaybeLocal maybe_instance = - Nan::NewInstance(constructor->GetFunction(), argc, argv); - if (maybe_instance.IsEmpty()) { - // There's probably a pending exception - return; - } else { - info.GetReturnValue().Set(maybe_instance.ToLocalChecked()); - return; - } - } - grpc_server *wrapped_server; - grpc_completion_queue *queue = GetCompletionQueue(); - grpc_channel_args *channel_args; - if (!ParseChannelArgs(info[0], &channel_args)) { - DeallocateChannelArgs(channel_args); - return Nan::ThrowTypeError( - "Server options must be an object with " - "string keys and integer or string values"); - } - wrapped_server = grpc_server_create(channel_args, NULL); - DeallocateChannelArgs(channel_args); - grpc_server_register_completion_queue(wrapped_server, queue, NULL); - Server *server = new Server(wrapped_server); - server->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); -} - -NAN_METHOD(Server::RequestCall) { - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError("requestCall can only be called on a Server"); - } - Server *server = ObjectWrap::Unwrap(info.This()); - NewCallOp *op = new NewCallOp(); - unique_ptr ops(new OpVec()); - ops->push_back(unique_ptr(op)); - grpc_call_error error = grpc_server_request_call( - server->wrapped_server, &op->call, &op->details, &op->request_metadata, - GetCompletionQueue(), GetCompletionQueue(), - new struct tag(new Callback(info[0].As()), ops.release(), NULL, - Nan::Null())); - if (error != GRPC_CALL_OK) { - return Nan::ThrowError(nanErrorWithCode("requestCall failed", error)); - } - CompletionQueueNext(); -} - -NAN_METHOD(Server::AddHttp2Port) { - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError("addHttp2Port can only be called on a Server"); - } - if (!info[0]->IsString()) { - return Nan::ThrowTypeError( - "addHttp2Port's first argument must be a String"); - } - if (!ServerCredentials::HasInstance(info[1])) { - return Nan::ThrowTypeError( - "addHttp2Port's second argument must be ServerCredentials"); - } - Server *server = ObjectWrap::Unwrap(info.This()); - ServerCredentials *creds_object = ObjectWrap::Unwrap( - Nan::To(info[1]).ToLocalChecked()); - grpc_server_credentials *creds = creds_object->GetWrappedServerCredentials(); - int port; - if (creds == NULL) { - port = grpc_server_add_insecure_http2_port(server->wrapped_server, - *Utf8String(info[0])); - } else { - port = grpc_server_add_secure_http2_port(server->wrapped_server, - *Utf8String(info[0]), creds); - } - info.GetReturnValue().Set(Nan::New(port)); -} - -NAN_METHOD(Server::Start) { - Nan::HandleScope scope; - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError("start can only be called on a Server"); - } - Server *server = ObjectWrap::Unwrap(info.This()); - grpc_server_start(server->wrapped_server); -} - -NAN_METHOD(Server::TryShutdown) { - Nan::HandleScope scope; - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError("tryShutdown can only be called on a Server"); - } - Server *server = ObjectWrap::Unwrap(info.This()); - if (server->wrapped_server == NULL) { - // Server is already shut down. Call callback immediately. - Nan::Callback callback(info[0].As()); - callback.Call(0, {}); - return; - } - TryShutdownOp *op = new TryShutdownOp(server, info.This()); - unique_ptr ops(new OpVec()); - ops->push_back(unique_ptr(op)); - grpc_server_shutdown_and_notify( - server->wrapped_server, GetCompletionQueue(), - new struct tag(new Nan::Callback(info[0].As()), ops.release(), - NULL, Nan::Null())); - CompletionQueueNext(); -} - -NAN_METHOD(Server::ForceShutdown) { - Nan::HandleScope scope; - if (!HasInstance(info.This())) { - return Nan::ThrowTypeError("forceShutdown can only be called on a Server"); - } - Server *server = ObjectWrap::Unwrap(info.This()); - server->ShutdownServer(); -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/server.h b/packages/grpc-native-core/ext/server.h deleted file mode 100644 index 66b3ac526..000000000 --- a/packages/grpc-native-core/ext/server.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef NET_GRPC_NODE_SERVER_H_ -#define NET_GRPC_NODE_SERVER_H_ - -#include -#include -#include "grpc/grpc.h" - -namespace grpc { -namespace node { - -/* Wraps grpc_server as a JavaScript object. Provides a constructor - and wrapper methods for grpc_server_create, grpc_server_request_call, - grpc_server_add_http2_port, and grpc_server_start. */ -class Server : public Nan::ObjectWrap { - public: - /* Initializes the Server class and exposes the constructor and - wrapper methods to JavaScript */ - static void Init(v8::Local exports); - /* Tests whether the given value was constructed by this class's - JavaScript constructor */ - static bool HasInstance(v8::Local val); - - void DestroyWrappedServer(); - - private: - explicit Server(grpc_server *server); - ~Server(); - - // Prevent copying - Server(const Server &); - Server &operator=(const Server &); - - void ShutdownServer(); - - static NAN_METHOD(New); - static NAN_METHOD(RequestCall); - static NAN_METHOD(AddHttp2Port); - static NAN_METHOD(Start); - static NAN_METHOD(TryShutdown); - static NAN_METHOD(ForceShutdown); - static Nan::Callback *constructor; - static Nan::Persistent fun_tpl; - - grpc_server *wrapped_server; - grpc_completion_queue *shutdown_queue; -}; - -} // namespace node -} // namespace grpc - -#endif // NET_GRPC_NODE_SERVER_H_ diff --git a/packages/grpc-native-core/ext/server_credentials.cc b/packages/grpc-native-core/ext/server_credentials.cc deleted file mode 100644 index b7fa47884..000000000 --- a/packages/grpc-native-core/ext/server_credentials.cc +++ /dev/null @@ -1,190 +0,0 @@ -/* - * - * 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. - * - */ - -#include - -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" -#include "grpc/support/log.h" -#include "server_credentials.h" - -namespace grpc { -namespace node { - -using Nan::Callback; -using Nan::EscapableHandleScope; -using Nan::HandleScope; -using Nan::Maybe; -using Nan::MaybeLocal; -using Nan::ObjectWrap; -using Nan::Persistent; -using Nan::Utf8String; - -using v8::Array; -using v8::Exception; -using v8::External; -using v8::Function; -using v8::FunctionTemplate; -using v8::Integer; -using v8::Local; -using v8::Object; -using v8::ObjectTemplate; -using v8::String; -using v8::Value; - -Nan::Callback *ServerCredentials::constructor; -Persistent ServerCredentials::fun_tpl; - -ServerCredentials::ServerCredentials(grpc_server_credentials *credentials) - : wrapped_credentials(credentials) {} - -ServerCredentials::~ServerCredentials() { - grpc_server_credentials_release(wrapped_credentials); -} - -void ServerCredentials::Init(Local exports) { - Nan::HandleScope scope; - Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("ServerCredentials").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Local ctr = tpl->GetFunction(); - Nan::Set( - ctr, Nan::New("createSsl").ToLocalChecked(), - Nan::GetFunction(Nan::New(CreateSsl)).ToLocalChecked()); - Nan::Set(ctr, Nan::New("createInsecure").ToLocalChecked(), - Nan::GetFunction(Nan::New(CreateInsecure)) - .ToLocalChecked()); - fun_tpl.Reset(tpl); - constructor = new Nan::Callback(ctr); - Nan::Set(exports, Nan::New("ServerCredentials").ToLocalChecked(), ctr); -} - -bool ServerCredentials::HasInstance(Local val) { - Nan::HandleScope scope; - return Nan::New(fun_tpl)->HasInstance(val); -} - -Local ServerCredentials::WrapStruct( - grpc_server_credentials *credentials) { - Nan::EscapableHandleScope scope; - const int argc = 1; - Local argv[argc] = { - Nan::New(reinterpret_cast(credentials))}; - MaybeLocal maybe_instance = - Nan::NewInstance(constructor->GetFunction(), argc, argv); - if (maybe_instance.IsEmpty()) { - return scope.Escape(Nan::Null()); - } else { - return scope.Escape(maybe_instance.ToLocalChecked()); - } -} - -grpc_server_credentials *ServerCredentials::GetWrappedServerCredentials() { - return wrapped_credentials; -} - -NAN_METHOD(ServerCredentials::New) { - if (info.IsConstructCall()) { - if (!info[0]->IsExternal()) { - return Nan::ThrowTypeError( - "ServerCredentials can only be created with the provided functions"); - } - Local ext = info[0].As(); - grpc_server_credentials *creds_value = - reinterpret_cast(ext->Value()); - ServerCredentials *credentials = new ServerCredentials(creds_value); - credentials->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - } else { - // This should never be called directly - return Nan::ThrowTypeError( - "ServerCredentials can only be created with the provided functions"); - } -} - -NAN_METHOD(ServerCredentials::CreateSsl) { - Nan::HandleScope scope; - char *root_certs = NULL; - if (::node::Buffer::HasInstance(info[0])) { - root_certs = ::node::Buffer::Data(info[0]); - } else if (!(info[0]->IsNull() || info[0]->IsUndefined())) { - return Nan::ThrowTypeError( - "createSSl's first argument must be a Buffer if provided"); - } - if (!info[1]->IsArray()) { - return Nan::ThrowTypeError( - "createSsl's second argument must be a list of objects"); - } - - // Default to not requesting the client certificate - grpc_ssl_client_certificate_request_type client_certificate_request = - GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE; - if (info[2]->IsBoolean()) { - client_certificate_request = - Nan::To(info[2]).FromJust() - ? GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY - : GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE; - } else if (!(info[2]->IsUndefined() || info[2]->IsNull())) { - return Nan::ThrowTypeError( - "createSsl's third argument must be a boolean if provided"); - } - Local pair_list = Local::Cast(info[1]); - uint32_t key_cert_pair_count = pair_list->Length(); - grpc_ssl_pem_key_cert_pair *key_cert_pairs = - new grpc_ssl_pem_key_cert_pair[key_cert_pair_count]; - - Local key_key = Nan::New("private_key").ToLocalChecked(); - Local cert_key = Nan::New("cert_chain").ToLocalChecked(); - - for (uint32_t i = 0; i < key_cert_pair_count; i++) { - Local pair_val = Nan::Get(pair_list, i).ToLocalChecked(); - if (!pair_val->IsObject()) { - delete[] key_cert_pairs; - return Nan::ThrowTypeError("Key/cert pairs must be objects"); - } - Local pair_obj = Nan::To(pair_val).ToLocalChecked(); - Local maybe_key = Nan::Get(pair_obj, key_key).ToLocalChecked(); - Local maybe_cert = Nan::Get(pair_obj, cert_key).ToLocalChecked(); - if (!::node::Buffer::HasInstance(maybe_key)) { - delete[] key_cert_pairs; - return Nan::ThrowTypeError("private_key must be a Buffer"); - } - if (!::node::Buffer::HasInstance(maybe_cert)) { - delete[] key_cert_pairs; - return Nan::ThrowTypeError("cert_chain must be a Buffer"); - } - key_cert_pairs[i].private_key = ::node::Buffer::Data(maybe_key); - key_cert_pairs[i].cert_chain = ::node::Buffer::Data(maybe_cert); - } - grpc_server_credentials *creds = grpc_ssl_server_credentials_create_ex( - root_certs, key_cert_pairs, key_cert_pair_count, - client_certificate_request, NULL); - delete[] key_cert_pairs; - if (creds == NULL) { - info.GetReturnValue().SetNull(); - } else { - info.GetReturnValue().Set(WrapStruct(creds)); - } -} - -NAN_METHOD(ServerCredentials::CreateInsecure) { - info.GetReturnValue().Set(WrapStruct(NULL)); -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/server_credentials.h b/packages/grpc-native-core/ext/server_credentials.h deleted file mode 100644 index 59f91481a..000000000 --- a/packages/grpc-native-core/ext/server_credentials.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef NET_GRPC_NODE_SERVER_CREDENTIALS_H_ -#define NET_GRPC_NODE_SERVER_CREDENTIALS_H_ - -#include -#include -#include "grpc/grpc.h" -#include "grpc/grpc_security.h" - -namespace grpc { -namespace node { - -/* Wrapper class for grpc_server_credentials structs */ -class ServerCredentials : public Nan::ObjectWrap { - public: - static void Init(v8::Local exports); - static bool HasInstance(v8::Local val); - /* Wrap a grpc_server_credentials struct in a javascript object */ - static v8::Local WrapStruct(grpc_server_credentials *credentials); - - /* Returns the grpc_server_credentials struct that this object wraps */ - grpc_server_credentials *GetWrappedServerCredentials(); - - private: - explicit ServerCredentials(grpc_server_credentials *credentials); - ~ServerCredentials(); - - // Prevent copying - ServerCredentials(const ServerCredentials &); - ServerCredentials &operator=(const ServerCredentials &); - - static NAN_METHOD(New); - static NAN_METHOD(CreateSsl); - static NAN_METHOD(CreateInsecure); - static Nan::Callback *constructor; - // Used for typechecking instances of this javascript class - static Nan::Persistent fun_tpl; - - grpc_server_credentials *wrapped_credentials; -}; - -} // namespace node -} // namespace grpc - -#endif // NET_GRPC_NODE_SERVER_CREDENTIALS_H_ diff --git a/packages/grpc-native-core/ext/slice.cc b/packages/grpc-native-core/ext/slice.cc deleted file mode 100644 index 8806a61a9..000000000 --- a/packages/grpc-native-core/ext/slice.cc +++ /dev/null @@ -1,90 +0,0 @@ -/* - * - * Copyright 2016 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. - * - */ - -#include -#include -#include -#include - -#include "slice.h" - -namespace grpc { -namespace node { - -using Nan::Persistent; - -using v8::Local; -using v8::String; -using v8::Value; - -namespace { -void SliceFreeCallback(char *data, void *hint) { - grpc_slice *slice = reinterpret_cast(hint); - grpc_slice_unref(*slice); - delete slice; -} - -void string_destroy_func(void *user_data) { - delete reinterpret_cast(user_data); -} - -void buffer_destroy_func(void *user_data) { - delete reinterpret_cast(user_data); -} -} // namespace - -grpc_slice CreateSliceFromString(const Local source) { - Nan::HandleScope scope; - Nan::Utf8String *utf8_value = new Nan::Utf8String(source); - return grpc_slice_new_with_user_data(**utf8_value, source->Length(), - string_destroy_func, utf8_value); -} - -grpc_slice CreateSliceFromBuffer(const Local source) { - // Prerequisite: ::node::Buffer::HasInstance(source) - Nan::HandleScope scope; - return grpc_slice_new_with_user_data( - ::node::Buffer::Data(source), ::node::Buffer::Length(source), - buffer_destroy_func, new PersistentValue(source)); -} -Local CopyStringFromSlice(const grpc_slice slice) { - Nan::EscapableHandleScope scope; - if (GRPC_SLICE_LENGTH(slice) == 0) { - return scope.Escape(Nan::EmptyString()); - } - return scope.Escape( - Nan::New(const_cast(reinterpret_cast( - GRPC_SLICE_START_PTR(slice))), - GRPC_SLICE_LENGTH(slice)) - .ToLocalChecked()); -} - -Local CreateBufferFromSlice(const grpc_slice slice) { - Nan::EscapableHandleScope scope; - grpc_slice *slice_ptr = new grpc_slice; - *slice_ptr = grpc_slice_ref(slice); - return scope.Escape( - Nan::NewBuffer( - const_cast( - reinterpret_cast(GRPC_SLICE_START_PTR(*slice_ptr))), - GRPC_SLICE_LENGTH(*slice_ptr), SliceFreeCallback, slice_ptr) - .ToLocalChecked()); -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/ext/timeval.cc b/packages/grpc-native-core/ext/timeval.cc deleted file mode 100644 index 614258475..000000000 --- a/packages/grpc-native-core/ext/timeval.cc +++ /dev/null @@ -1,53 +0,0 @@ -/* - * - * 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. - * - */ - -#include -#include - -#include "grpc/grpc.h" -#include "grpc/support/time.h" -#include "timeval.h" - -namespace grpc { -namespace node { - -gpr_timespec MillisecondsToTimespec(double millis) { - if (millis == std::numeric_limits::infinity()) { - return gpr_inf_future(GPR_CLOCK_REALTIME); - } else if (millis == -std::numeric_limits::infinity()) { - return gpr_inf_past(GPR_CLOCK_REALTIME); - } else { - return gpr_time_from_micros(static_cast(millis * 1000), - GPR_CLOCK_REALTIME); - } -} - -double TimespecToMilliseconds(gpr_timespec timespec) { - timespec = gpr_convert_clock_type(timespec, GPR_CLOCK_REALTIME); - if (gpr_time_cmp(timespec, gpr_inf_future(GPR_CLOCK_REALTIME)) == 0) { - return std::numeric_limits::infinity(); - } else if (gpr_time_cmp(timespec, gpr_inf_past(GPR_CLOCK_REALTIME)) == 0) { - return -std::numeric_limits::infinity(); - } else { - return (static_cast(timespec.tv_sec) * 1000 + - static_cast(timespec.tv_nsec) / 1000000); - } -} - -} // namespace node -} // namespace grpc diff --git a/packages/grpc-native-core/gulpfile.js b/packages/grpc-native-core/gulpfile.js deleted file mode 100644 index 8a714ee62..000000000 --- a/packages/grpc-native-core/gulpfile.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2017 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. - * - */ - -const _gulp = require('gulp'); -const help = require('gulp-help'); - -// gulp-help monkeypatches tasks to have an additional description parameter -const gulp = help(_gulp); - -const jsdoc = require('gulp-jsdoc3'); -const jshint = require('gulp-jshint'); -const mocha = require('gulp-mocha'); -const execa = require('execa'); -const path = require('path'); -const del = require('del'); - -const nativeCoreDir = __dirname; -const srcDir = path.resolve(nativeCoreDir, 'src'); -const testDir = path.resolve(nativeCoreDir, 'test'); - -const pkg = require('./package'); -const jshintConfig = pkg.jshintConfig; - -gulp.task('native.core.clean', 'Delete generated files', () => { - return del([path.resolve(nativeCoreDir, 'build'), - path.resolve(nativeCoreDir, 'ext/node')]); -}); - -gulp.task('native.core.clean.all', 'Delete all files created by tasks', - ['native.core.clean']); - -gulp.task('native.core.install', 'Install native core dependencies', () => { - return execa('npm', ['install', '--build-from-source', '--unsafe-perm'], - {cwd: nativeCoreDir, stdio: 'inherit'}); -}); - -gulp.task('native.core.install.windows', 'Install native core dependencies for MS Windows', () => { - return execa('npm', ['install', '--build-from-source'], - {cwd: nativeCoreDir, stdio: 'inherit'}).catch(() => -del(path.resolve(process.env.USERPROFILE, '.node-gyp', process.versions.node, 'include/node/openssl'), { force: true }).then(() => -execa('npm', ['install', '--build-from-source'], - {cwd: nativeCoreDir, stdio: 'inherit'}) - )) -}); - -gulp.task('native.core.link.create', 'Create npm link', () => { - return execa('npm', ['link'], {cwd: nativeCoreDir, stdio: 'inherit'}); -}); - -gulp.task('native.core.lint', 'Emits linting errors', () => { - return gulp.src([`${nativeCoreDir}/index.js`, `${srcDir}/*.js`, `${testDir}/*.js`]) - .pipe(jshint(pkg.jshintConfig)) - .pipe(jshint.reporter('default')); -}); - -gulp.task('native.core.build', 'Build native package', () => { - return execa('npm', ['run', 'build'], {cwd: nativeCoreDir, stdio: 'inherit'}); -}); - -gulp.task('native.core.test', 'Run all tests', ['native.core.build'], () => { - return gulp.src(`${testDir}/*.js`).pipe(mocha({reporter: 'mocha-jenkins-reporter'})); -}); - -gulp.task('native.core.doc.gen', 'Generate docs', (cb) => { - var config = require('./jsdoc_conf.json'); - gulp.src([`${nativeCoreDir}/README.md`, `${nativeCoreDir}/index.js`, `${srcDir}/*.js`], {read: false}) - .pipe(jsdoc(config, cb)); -}); diff --git a/packages/grpc-native-core/index.d.ts b/packages/grpc-native-core/index.d.ts deleted file mode 100644 index f3a091eb5..000000000 --- a/packages/grpc-native-core/index.d.ts +++ /dev/null @@ -1,1221 +0,0 @@ -declare module "grpc" { - import { Message, Service } from "protobufjs"; - import { Duplex, Readable, Writable } from "stream"; - import { SecureContext } from "tls"; - - /** - * Load a ProtoBuf.js object as a gRPC object. - * @param value The ProtoBuf.js reflection object to load - * @param options Options to apply to the loaded file - * @return The resulting gRPC object. - */ - export function loadObject(value: object, options?: LoadObjectOptions): GrpcObject; - - /** - * Options for loading proto object as gRPC object - * @param {(number|string)=} [options.protobufjsVersion='detect'] 5 and 6 - * respectively indicate that an object from the corresponding version of - * Protobuf.js is provided in the value argument. If the option is 'detect', - * gRPC will guess what the version is based on the structure of the value. - */ - export interface LoadObjectOptions { - /** - * Deserialize bytes values as base64 strings instead of Buffers. - * Defaults to `false`. - */ - binaryAsBase64?: boolean; - - /** - * Deserialize long values as strings instead of objects. - * Defaults to `true`. - */ - longsAsStrings?: boolean; - - /** - * Deserialize enum values as strings instead of numbers. Only works with - * Protobuf.js 6 values. - * Defaults to `true`. - */ - enumsAsStrings?: boolean; - - /** - * use the beta method argument order for client methods, with optional - * arguments after the callback. This option is only a temporary stopgap - * measure to smooth an API breakage. It is deprecated, and new code - * should not use it. - * Defaults to `false` - */ - deprecatedArgumentOrder?: boolean; - - /** - * 5 and 6 respectively indicate that an object from the corresponding - * version of Protobuf.js is provided in the value argument. If the option - * is 'detect', gRPC wll guess what the version is based on the structure - * of the value. - */ - protobufjsVersion?: 5 | 6 | "detect"; - } - - /** - * Map from `.proto` file. - * - Namespaces become maps from the names of their direct members to those member objects - * - Service definitions become client constructors for clients for that service. They also - * have a service member that can be used for constructing servers. - * - Message definitions become Message constructors like those that ProtoBuf.js would create - * - Enum definitions become Enum objects like those that ProtoBuf.js would create - * - Anything else becomes the relevant reflection object that ProtoBuf.js would create - */ - export interface GrpcObject { - [name: string]: GrpcObject | typeof Client | Message; - } - - /** - * Load a gRPC object from a .proto file. - * @param filename The file to load - * @param format The file format to expect. Defaults to 'proto' - * @param options Options to apply to the loaded file - * @return The resulting gRPC object - */ - export function load(filename: Filename, format?: "proto" | "json", options?: LoadOptions): GrpcObject; - - /** - * A filename - */ - export type Filename = string | { root: string, file: string }; - - /** - * Options for loading proto file as gRPC object - */ - export interface LoadOptions { - /** - * Load this file with field names in camel case instead of their original case. - * Defaults to `false`. - */ - convertFieldsToCamelCase?: boolean; - - /** - * Deserialize bytes values as base64 strings instead of Buffers. - * Defaults to `false`. - */ - binaryAsBase64?: boolean; - - /** - * Deserialize long values as strings instead of objects. - * Defaults to `true`. - */ - longsAsStrings?: boolean; - - /** - * Use the beta method argument order for client methods, with optional - * arguments after the callback. This option is only a temporary stopgap - * measure to smooth an API breakage. It is deprecated, and new code - * should not use it. - * Defaults to `false` - */ - deprecatedArgumentOrder?: boolean; - } - - /** - * Sets the logger function for the gRPC module. For debugging purposes, the C - * core will log synchronously directly to stdout unless this function is - * called. Note: the output format here is intended to be informational, and - * is not guaranteed to stay the same in the future. - * Logs will be directed to logger.error. - * @param logger A Console-like object. - */ - export function setLogger(logger: Console): void; - - /** - * Sets the logger verbosity for gRPC module logging. The options are members - * of the grpc.logVerbosity map. - * @param verbosity The minimum severity to log - */ - export function setLogVerbosity(verbosity: logVerbosity): void; - - /** - * Server object that stores request handlers and delegates incoming requests to those handlers - */ - export class Server { - /** - * Constructs a server object that stores request handlers and delegates - * incoming requests to those handlers - * @param options Options that should be passed to the internal server - * implementation - * ``` - * var server = new grpc.Server(); - * server.addProtoService(protobuf_service_descriptor, service_implementation); - * server.bind('address:port', server_credential); - * server.start(); - * ``` - */ - constructor(options?: object); - - /** - * Start the server and begin handling requests - */ - start(): void; - - /** - * Registers a handler to handle the named method. Fails if there already is - * a handler for the given method. Returns true on success - * @param name The name of the method that the provided function should - * handle/respond to. - * @param handler Function that takes a stream of - * request values and returns a stream of response values - * @param serialize Serialization function for responses - * @param deserialize Deserialization function for requests - * @param type The streaming type of method that this handles - * @return True if the handler was set. False if a handler was already - * set for that name. - */ - register(name: string, handler: handleCall, serialize: serialize, deserialize: deserialize, type: string): boolean; - - /** - * Gracefully shuts down the server. The server will stop receiving new calls, - * and any pending calls will complete. The callback will be called when all - * pending calls have completed and the server is fully shut down. This method - * is idempotent with itself and forceShutdown. - * @param {function()} callback The shutdown complete callback - */ - tryShutdown(callback: () => void): void; - - /** - * Forcibly shuts down the server. The server will stop receiving new calls - * and cancel all pending calls. When it returns, the server has shut down. - * This method is idempotent with itself and tryShutdown, and it will trigger - * any outstanding tryShutdown callbacks. - */ - forceShutdown(): void; - - /** - * Add a service to the server, with a corresponding implementation. - * @param service The service descriptor - * @param implementation Map of method names to method implementation - * for the provided service. - */ - addService(service: Service, implementation: { [name: string]: handleCall }): void; - - /** - * Add a proto service to the server, with a corresponding implementation - * @deprecated Use `Server#addService` instead - * @param service The proto service descriptor - * @param implementation Map of method names to method implementation - * for the provided service. - */ - addProtoService(service: Service | ServiceDefinition, implementation: { [name: string]: handleCall }): void; - - /** - * Binds the server to the given port, with SSL disabled if creds is an - * insecure credentials object - * @param port The port that the server should bind on, in the format - * "address:port" - * @param creds Server credential object to be used for SSL. Pass an - * insecure credentials object for an insecure port. - * @return The bound port number or 0 if the opreation failed. - */ - bind(port: string, creds: ServerCredentials): number; - } - - /** - * An object that completely defines a service. - * @typedef {Object.} grpc~ServiceDefinition - */ - export interface ServiceDefinition { - [s: string]: MethodDefinition; - } - - /** - * An object that completely defines a service method signature. - */ - export interface MethodDefinition { - /** - * The method's URL path - */ - path: string; - /** - * Indicates whether the method accepts a stream of requests - */ - requestStream: boolean; - /** - * Indicates whether the method returns a stream of responses - */ - responseStream: boolean; - /** - * Serialization function for request values - */ - requestSerialize: serialize; - /** - * Serialization function for response values - */ - responseSerialize: serialize; - /** - * Deserialization function for request data - */ - requestDeserialize: deserialize; - /** - * Deserialization function for repsonse data - */ - responseDeserialize: deserialize; - } - - type handleCall = handleUnaryCall | handleClientStreamingCall | handleServerStreamingCall | handleBidiStreamingCall; - - /** - * User-provided method to handle unary requests on a server - */ - type handleUnaryCall = (call: ServerUnaryCall, callback: sendUnaryData) => void; - - /** - * An EventEmitter. Used for unary calls. - */ - export class ServerUnaryCall { - /** - * Indicates if the call has been cancelled - */ - cancelled: boolean; - - /** - * The request metadata from the client - */ - metadata: Metadata; - - /** - * The request message from the client - */ - request: any; - - private constructor(); - - /** - * Get the endpoint this call/stream is connected to. - * @return The URI of the endpoint - */ - getPeer(): string; - - /** - * Send the initial metadata for a writable stream. - * @param responseMetadata Metadata to send - */ - sendMetadata(responseMetadata: Metadata): void; - } - - /** - * User provided method to handle client streaming methods on the server. - */ - type handleClientStreamingCall = (call: ServerReadableStream, callback: sendUnaryData) => void; - - /** - * A stream that the server can read from. Used for calls that are streaming - * from the client side. - */ - export class ServerReadableStream extends Readable { - /** - * Indicates if the call has been cancelled - */ - cancelled: boolean; - - /** - * The request metadata from the client - */ - metadata: Metadata; - - private constructor(); - - /** - * Get the endpoint this call/stream is connected to. - * @return The URI of the endpoint - */ - getPeer(): string; - - /** - * Send the initial metadata for a writable stream. - * @param responseMetadata Metadata to send - */ - sendMetadata(responseMetadata: Metadata): void; - } - - /** - * User provided method to handle server streaming methods on the server. - */ - type handleServerStreamingCall = (call: ServerWriteableStream) => void; - - /** - * A stream that the server can write to. Used for calls that are streaming - * from the server side. - */ - export class ServerWriteableStream extends Writable { - /** - * Indicates if the call has been cancelled - */ - cancelled: boolean; - - /** - * The request metadata from the client - */ - metadata: Metadata; - - /** - * The request message from the client - */ - request: any; - - private constructor(); - - /** - * Get the endpoint this call/stream is connected to. - * @return The URI of the endpoint - */ - getPeer(): string; - - /** - * Send the initial metadata for a writable stream. - * @param responseMetadata Metadata to send - */ - sendMetadata(responseMetadata: Metadata): void; - } - - /** - * User provided method to handle bidirectional streaming calls on the server. - */ - type handleBidiStreamingCall = (call: ServerDuplexStream) => void; - - /** - * A stream that the server can read from or write to. Used for calls - * with duplex streaming. - */ - export class ServerDuplexStream extends Duplex { - private constructor(); - - /** - * Get the endpoint this call/stream is connected to. - * @return The URI of the endpoint - */ - getPeer(): string; - - /** - * Send the initial metadata for a writable stream. - * @param responseMetadata Metadata to send - */ - sendMetadata(responseMetadata: Metadata): void; - } - - /** - * A deserialization function - * @param data The byte sequence to deserialize - * @return The data deserialized as a value - */ - type deserialize = (data: Buffer) => any; - - /** - * A serialization function - * @param value The value to serialize - * @return The value serialized as a byte sequence - */ - type serialize = (value: any) => Buffer; - - /** - * Callback function passed to server handlers that handle methods with - * unary responses. - */ - type sendUnaryData = (error: ServiceError | null, value: any, trailer?: Metadata, flags?: number) => void; - - /** - * A class for storing metadata. Keys are normalized to lowercase ASCII. - */ - export class Metadata { - /** - * Sets the given value for the given key by replacing any other values - * associated with that key. Normalizes the key. - * @param key The key to whose value should be set. - * @param value The value to set. Must be a buffer if and only - * if the normalized key ends with '-bin'. - */ - set(key: string, value: MetadataValue): void; - - /** - * Adds the given value for the given key by appending to a list of previous - * values associated with that key. Normalizes the key. - * @param key The key for which a new value should be appended. - * @param value The value to add. Must be a buffer if and only - * if the normalized key ends with '-bin'. - */ - add(key: string, value: MetadataValue): void; - - /** - * Removes the given key and any associated values. Normalizes the key. - * @param key The key whose values should be removed. - */ - remove(key: string): void; - - /** - * Gets a list of all values associated with the key. Normalizes the key. - * @param key The key whose value should be retrieved. - * @return A list of values associated with the given key. - */ - get(key: string): MetadataValue[]; - - /** - * Gets a plain object mapping each key to the first value associated with it. - * This reflects the most common way that people will want to see metadata. - * @return A key/value mapping of the metadata. - */ - getMap(): { [key: string]: MetadataValue }; - - /** - * Clones the metadata object. - * @return The newly cloned object. - */ - clone(): Metadata; - } - - export type MetadataValue = string | Buffer; - - /** - * Represents the status of a completed request. If `code` is - * `grpc.status.OK`, then the request has completed successfully. - * Otherwise, the request has failed, `details` will contain a description of - * the error. Either way, `metadata` contains the trailing response metadata - * sent by the server when it finishes processing the call. - */ - export interface StatusObject { - /** - * The error code, a key of `grpc.status` - */ - code: status; - /** - * Human-readable description of the status - */ - details: string; - /** - * Trailing metadata sent with the status, if applicable - */ - metadata: Metadata; - } - - /** - * Describes how a request has failed. The member `message` will be the same as - * `details` in `StatusObject`, and `code` and `metadata` are the - * same as in that object. - */ - export interface ServiceError extends Error { - /** - * The error code, a key of {@link grpc.status} that is not `grpc.status.OK` - */ - code?: status; - /** - * Trailing metadata sent with the status, if applicable - */ - metadata?: Metadata; - } - - /** - * ServerCredentials factories - */ - export class ServerCredentials { - /** - * Create insecure server credentials - * @return The ServerCredentials - */ - static createInsecure(): ServerCredentials; - /** - * Create SSL server credentials - * @param rootCerts Root CA certificates for validating client certificates - * @param keyCertPairs A list of private key and certificate chain pairs to - * be used for authenticating the server - * @param checkClientCertificate Indicates that the server should request - * and verify the client's certificates. - * Defaults to `false`. - * @return The ServerCredentials - */ - static createInsecure(rootCerts: Buffer | null, keyCertPairs: KeyCertPair[], checkClientCertificate?: boolean): ServerCredentials; - } - - /** - * A private key and certificate pair - */ - export interface KeyCertPair { - /** - * The server's private key - */ - privateKey: Buffer; - - /** - * The server's certificate chain - */ - certChain: Buffer; - } - - /** - * Enum of status codes that gRPC can return - */ - export enum status { - /** - * Not an error; returned on success - */ - OK = 0, - /** - * The operation was cancelled (typically by the caller). - */ - 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. - */ - UNKNOWN = 2, - /** - * Client specified an invalid argument. Note that this differs - * from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments - * that are problematic regardless of the state of the system - * (e.g., a malformed file name). - */ - INVALID_ARGUMENT = 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. - */ - DEADLINE_EXCEEDED = 4, - /** - * Some requested entity (e.g., file or directory) was not found. - */ - NOT_FOUND = 5, - /** - * Some entity that we attempted to create (e.g., file or directory) - * already exists. - */ - ALREADY_EXISTS = 6, - /** - * The caller does not have permission to execute the specified - * operation. PERMISSION_DENIED must not be used for rejections - * caused by exhausting some resource (use RESOURCE_EXHAUSTED - * instead for those errors). PERMISSION_DENIED must not be - * used if the caller can not be identified (use UNAUTHENTICATED - * instead for those errors). - */ - PERMISSION_DENIED = 7, - /** - * Some resource has been exhausted, perhaps a per-user quota, or - * perhaps the entire file system is out of space. - */ - RESOURCE_EXHAUSTED = 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 FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: - * - * - Use UNAVAILABLE if the client can retry just the failing call. - * - Use ABORTED if the client should retry at a higher-level - * (e.g., restarting a read-modify-write sequence). - * - Use FAILED_PRECONDITION 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, FAILED_PRECONDITION - * should be returned since the client should not retry unless - * they have first fixed up the directory by deleting files from it. - * - Use FAILED_PRECONDITION if the client performs conditional - * REST Get/Update/Delete on a resource and the resource on the - * server does not match the condition. E.g., conflicting - * read-modify-write on the same resource. - */ - FAILED_PRECONDITION = 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 FAILED_PRECONDITION, - * ABORTED, and UNAVAILABLE. - */ - ABORTED = 10, - /** - * Operation was attempted past the valid range. E.g., seeking or - * reading past end of file. - * - * Unlike INVALID_ARGUMENT, this error indicates a problem that may - * be fixed if the system state changes. For example, a 32-bit file - * system will generate INVALID_ARGUMENT if asked to read at an - * offset that is not in the range [0,2^32-1], but it will generate - * OUT_OF_RANGE if asked to read from an offset past the current - * file size. - * - * There is a fair bit of overlap between FAILED_PRECONDITION and - * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific - * error) when it applies so that callers who are iterating through - * a space can easily look for an OUT_OF_RANGE error to detect when - * they are done. - */ - OUT_OF_RANGE = 11, - /** - * Operation is not implemented or not supported/enabled in this service. - */ - 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. - */ - 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 FAILED_PRECONDITION, - * ABORTED, and UNAVAILABLE. - */ - UNAVAILABLE = 14, - /** - * Unrecoverable data loss or corruption. - */ - DATA_LOSS = 15, - /** - * The request does not have valid authentication credentials for the - * operation. - */ - UNAUTHENTICATED = 16, - } - - /** - * Propagation flags: these can be bitwise or-ed to form the propagation option - * for calls. - * - * Users are encouraged to write propagation masks as deltas from the default. - * i.e. write `grpc.propagate.DEFAULTS & ~grpc.propagate.DEADLINE` to disable - * deadline propagation. - */ - export enum propagate { - DEADLINE, - CENSUS_STATS_CONTEXT, - CENSUS_TRACING_CONTEXT, - CANCELLATION, - DEFAULTS, - } - - /** - * Call error constants. Call errors almost always indicate bugs in the gRPC - * library, and these error codes are mainly useful for finding those bugs. - */ - export enum callError { - OK, - ERROR, - NOT_ON_SERVER, - NOT_ON_CLIENT, - ALREADY_INVOKED, - NOT_INVOKED, - ALREADY_FINISHED, - TOO_MANY_OPERATIONS, - INVALID_FLAGS, - INVALID_METADATA, - INVALID_MESSAGE, - NOT_SERVER_COMPLETION_QUEUE, - BATCH_TOO_BIG, - PAYLOAD_TYPE_MISMATCH, - } - - /** - * Write flags: these can be bitwise or-ed to form write options that modify - * how data is written. - */ - export enum writeFlags { - /** - * Hint that the write may be buffered and need not go out on the wire - * immediately. GRPC is free to buffer the message until the next non-buffered - * write, or until writes_done, but it need not buffer completely or at all. - */ - BUFFER_HINT = 1, - /** - * Force compression to be disabled for a particular write - */ - NO_COMPRESS, - } - - /** - * Log verbosity constants. Maps setting names to code numbers. - */ - export enum logVerbosity { - DEBUG, - INFO, - ERROR, - } - - /** - * Credentials module - * - * This module contains factory methods for two different credential types: - * CallCredentials and ChannelCredentials. ChannelCredentials are things like - * SSL credentials that can be used to secure a connection, and are used to - * construct a Client object. CallCredentials genrally modify metadata, so they - * can be attached to an individual method call. - * - * CallCredentials can be composed with other CallCredentials to create - * CallCredentials. ChannelCredentials can be composed with CallCredentials - * to create ChannelCredentials. No combined credential can have more than - * one ChannelCredentials. - * - * For example, to create a client secured with SSL that uses Google - * default application credentials to authenticate: - * - * ``` - * var channel_creds = credentials.createSsl(root_certs); - * (new GoogleAuth()).getApplicationDefault(function(err, credential) { - * var call_creds = credentials.createFromGoogleCredential(credential); - * var combined_creds = credentials.combineChannelCredentials( - * channel_creds, call_creds); - * var client = new Client(address, combined_creds); - * }); - * ``` - */ - export const credentials: { - /** - * Create an SSL Credentials object. If using a client-side certificate, both - * the second and third arguments must be passed. - * @param rootCerts The root certificate data - * @param privateKey The client certificate private key, if applicable - * @param certChain The client certificate cert chain, if applicable - * @return The SSL Credentials object - */ - createSsl(rootCerts?: Buffer, privateKey?: Buffer, certChain?: Buffer): ChannelCredentials; - - /** - * Create a gRPC credentials object from a metadata generation function. This - * function gets the service URL and a callback as parameters. The error - * passed to the callback can optionally have a 'code' value attached to it, - * which corresponds to a status code that this library uses. - * @param metadataGenerator The function that generates metadata - * @return The credentials object - */ - createFromMetadataGenerator(metadataGenerator: metadataGenerator): CallCredentials; - - /** - * Create a gRPC credential from a Google credential object. - * @param googleCredential The Google credential object to use - * @return The resulting credentials object - */ - createFromGoogleCredential(googleCredential: GoogleOAuth2Client): CallCredentials; - - /** - * Combine a ChannelCredentials with any number of CallCredentials into a single - * ChannelCredentials object. - * @param channelCredential The ChannelCredentials to start with - * @param credentials The CallCredentials to compose - * @return A credentials object that combines all of the input credentials - */ - combineChannelCredentials(channelCredential: ChannelCredentials, ...credentials: CallCredentials[]): ChannelCredentials; - - /** - * Combine any number of CallCredentials into a single CallCredentials object - * @param credentials The CallCredentials to compose - * @return A credentials object that combines all of the input credentials - */ - combineCallCredentials(...credentials: CallCredentials[]): CallCredentials; - - /** - * Create an insecure credentials object. This is used to create a channel that - * does not use SSL. This cannot be composed with anything. - * @return The insecure credentials object - */ - createInsecure(): ChannelCredentials; - }; - - /** - * Metadata generator function. - */ - export type metadataGenerator = (params: { service_url: string }, callback: (error: Error | null, metadata?: Metadata) => void) => void; - - /** - * This cannot be constructed directly. Instead, instances of this class should - * be created using the factory functions in `grpc.credentials` - */ - export interface ChannelCredentials { - /** - * Returns a copy of this object with the included set of per-call credentials - * expanded to include callCredentials. - * @param callCredentials A CallCredentials object to associate with this - * instance. - */ - compose(callCredentials: CallCredentials): ChannelCredentials; - - /** - * Gets the set of per-call credentials associated with this instance. - */ - getCallCredentials(): CallCredentials; - - /** - * Gets a SecureContext object generated from input parameters if this - * instance was created with createSsl, or null if this instance was created - * with createInsecure. - */ - getSecureContext(): SecureContext | null; - } - - /** - * This cannot be constructed directly. Instead, instances of this class should - * be created using the factory functions in `grpc.credentials` - */ - export interface CallCredentials { - /** - * Asynchronously generates a new Metadata object. - * @param options Options used in generating the Metadata object. - */ - generateMetadata(options: object): Promise; - - /** - * Creates a new CallCredentials object from properties of both this and - * another CallCredentials object. This object's metadata generator will be - * called first. - * @param callCredentials The other CallCredentials object. - */ - compose(callCredentials: CallCredentials): CallCredentials; - } - - /** - * This is the required interface from the OAuth2Client object - * from https://github.com/google/google-auth-library-nodejs lib. - * The definition is copied from `ts/lib/auth/oauth2client.ts` - */ - export interface GoogleOAuth2Client { - getRequestMetadata(optUri: string, metadataCallback: (err: Error, headers: any) => void): void; - } - - /** - * Creates a constructor for a client with the given methods, as specified in - * the methods argument. The resulting class will have an instance method for - * each method in the service, which is a partial application of one of the - * `grpc.Client` request methods, depending on `requestSerialize` - * and `responseSerialize`, with the `method`, `serialize`, and `deserialize` - * arguments predefined. - * @param methods An object mapping method names to method attributes - * @param serviceName The fully qualified name of the service - * @param classOptions An options object. - * @return New client constructor, which is a subclass of `grpc.Client`, and - * has the same arguments as that constructor. - */ - export function makeGenericClientConstructor( - methods: ServiceDefinition, - serviceName: string, - classOptions: GenericClientOptions, - ): typeof Client; - - /** - * Options for generic client constructor. - */ - export interface GenericClientOptions { - /** - * Indicates that the old argument order should be used for methods, with - * optional arguments at the end instead of the callback at the end. This - * option is only a temporary stopgap measure to smooth an API breakage. - * It is deprecated, and new code should not use it. - */ - deprecatedArgumentOrder?: boolean; - } - - /** - * Create a client with the given methods - */ - export class Client { - /** - * A generic gRPC client. Primarily useful as a base class for generated clients - * @param address Server address to connect to - * @param credentials Credentials to use to connect to the server - * @param options Options to apply to channel creation - */ - constructor(address: string, credentials: ChannelCredentials, options?: object) - - /** - * Make a unary request to the given method, using the given serialize - * and deserialize functions, with the given argument. - * @param method The name of the method to request - * @param serialize The serialization function for inputs - * @param deserialize The deserialization function for outputs - * @param argument The argument to the call. Should be serializable with - * serialize - * @param metadata Metadata to add to the call - * @param options Options map - * @param callback The callback to for when the response is received - * @return An event emitter for stream related events - */ - makeUnaryRequest( - method: string, - serialize: serialize, - deserialize: deserialize, - argument: any | null, - metadata: Metadata | null, - options: CallOptions | null, - callback: requestCallback, - ): ClientUnaryCall; - - /** - * Make a client stream request to the given method, using the given serialize - * and deserialize functions, with the given argument. - * @param method The name of the method to request - * @param serialize The serialization function for inputs - * @param deserialize The deserialization function for outputs - * @param metadata Array of metadata key/value pairs to add to the call - * @param options Options map - * @param callback The callback to for when the response is received - * @return An event emitter for stream related events - */ - makeClientStreamRequest( - method: string, - serialize: serialize, - deserialize: deserialize, - metadata: Metadata | null, - options: CallOptions | null, - callback: requestCallback, - ): ClientWritableStream; - - /** - * Make a server stream request to the given method, with the given serialize - * and deserialize function, using the given argument - * @param method The name of the method to request - * @param serialize The serialization function for inputs - * @param deserialize The deserialization function for outputs - * @param argument The argument to the call. Should be serializable with - * serialize - * @param metadata Array of metadata key/value pairs to add to the call - * @param options Options map - * @return An event emitter for stream related events - */ - makeServerStreamRequest( - method: string, - serialize: serialize, - deserialize: deserialize, - argument: any, - metadata?: Metadata | null, - options?: CallOptions | null, - ): ClientReadableStream; - - /** - * Make a bidirectional stream request with this method on the given channel. - * @param method The name of the method to request - * @param serialize The serialization function for inputs - * @param deserialize The deserialization - * function for outputs - * @param metadata Array of metadata key/value - * pairs to add to the call - * @param options Options map - * @return An event emitter for stream related events - */ - makeBidiStreamRequest( - method: string, - serialize: serialize, - deserialize: deserialize, - metadata?: Metadata | null, - options?: CallOptions | null, - ): ClientDuplexStream; - - /** - * Close this client. - */ - close(): void; - - /** - * Return the underlying channel object for the specified client - * @return The channel - */ - getChannel(): Channel; - - /** - * Wait for the client to be ready. The callback will be called when the - * client has successfully connected to the server, and it will be called - * with an error if the attempt to connect to the server has unrecoverablly - * failed or if the deadline expires. This function will make the channel - * start connecting if it has not already done so. - * @param deadline When to stop waiting for a connection. - * @param callback The callback to call when done attempting to connect. - */ - waitForReady(deadline: Deadline, callback: (error: Error | null) => void): void; - } - - /** - * A gRPC channel. - */ - export type Channel = any; - - /** - * Options that can be set on a call. - */ - export interface CallOptions { - /** - * The deadline for the entire call to complete. - */ - deadline?: Deadline; - /** - * Server hostname to set on the call. Only meaningful if different from - * the server address used to construct the client. - */ - host?: string; - /** - * Parent call. Used in servers when making a call as part of the process - * of handling a call. Used to propagate some information automatically, - * as specified by propagate_flags. - */ - parent?: Call; - /** - * Indicates which properties of a parent call should propagate to this - * call. Bitwise combination of flags in `grpc.propagate`. - */ - propagate_flags: number; - /** - * The credentials that should be used to make this particular call. - */ - credentials: CallCredentials; - } - - /** - * The deadline of an operation. If it is a date, the deadline is reached at - * the date and time specified. If it is a finite number, it is treated as - * a number of milliseconds since the Unix Epoch. If it is Infinity, the - * deadline will never be reached. If it is -Infinity, the deadline has already - * passed. - */ - export type Deadline = number | Date; - - /** - * Any client call type - */ - type Call = ClientUnaryCall | ClientReadableStream | ClientWritableStream | ClientDuplexStream; - - /** - * An EventEmitter. Used for unary calls. - */ - export class ClientUnaryCall { - private constructor(); - - /** - * Cancel the ongoing call. Results in the call ending with a CANCELLED status, - * unless it has already ended with some other status. - */ - cancel(): void; - - /** - * Get the endpoint this call/stream is connected to. - * @return The URI of the endpoint - */ - getPeer(): string; - } - - /** - * A stream that the client can read from. Used for calls that are streaming - * from the server side. - */ - export class ClientReadableStream extends Readable { - private constructor(); - - /** - * Cancel the ongoing call. Results in the call ending with a CANCELLED status, - * unless it has already ended with some other status. - */ - cancel(): void; - - /** - * Get the endpoint this call/stream is connected to. - * @return The URI of the endpoint - */ - getPeer(): string; - } - - /** - * A stream that the client can write to. Used for calls that are streaming from - * the client side. - */ - export class ClientWritableStream extends Writable { - private constructor(); - - /** - * Write a message to the request stream. If serializing the argument fails, - * the call will be cancelled and the stream will end with an error. - * @param message The message to write. Must be a valid argument to the - * serialize function of the corresponding method - * @param flags Flags to modify how the message is written - * @param callback Callback for when this chunk of data is flushed - * @return As defined for [Writable]{@link external:Writable} - */ - write(message: any, flags?: any&writeFlags, callback?: Function): boolean; - - /** - * Cancel the ongoing call. Results in the call ending with a CANCELLED status, - * unless it has already ended with some other status. - */ - cancel(): void; - - /** - * Get the endpoint this call/stream is connected to. - * @return The URI of the endpoint - */ - getPeer(): string; - } - - /** - * A stream that the client can read from or write to. Used for calls with - * duplex streaming. - */ - export class ClientDuplexStream extends Duplex { - private constructor(); - - /** - * Write a message to the request stream. If serializing the argument fails, - * the call will be cancelled and the stream will end with an error. - * @param message The message to write. Must be a valid argument to the - * serialize function of the corresponding method - * @param flags Flags to modify how the message is written - * @param callback Callback for when this chunk of data is flushed - * @return As defined for [Writable]{@link external:Writable} - */ - write(message: any, flags?: any&writeFlags, callback?: Function): boolean; - - /** - * Cancel the ongoing call. Results in the call ending with a CANCELLED status, - * unless it has already ended with some other status. - */ - cancel(): void; - - /** - * Get the endpoint this call/stream is connected to. - * @return The URI of the endpoint - */ - getPeer(): string; - } - - /** - * Client request callback - * @param error The error, if the call failed - * @param value The response value, if the call succeeded - */ - export type requestCallback = (error: ServiceError | null, value: any) => void; - - /** - * Return the underlying channel object for the specified client - * @see grpc.Client#getChannel - * @param client The client - * @return The channel - */ - export function getClientChannel(client: Client): Channel; - - /** - * Wait for the client to be ready. The callback will be called when the - * client has successfully connected to the server, and it will be called - * with an error if the attempt to connect to the server has unrecoverablly - * failed or if the deadline expires. This function will make the channel - * start connecting if it has not already done so. - * @see grpc.Client#waitForReady - * @param client The client to wait on - * @param deadline When to stop waiting for a connection. Pass Infinity to - * wait forever. - * @param callback The callback to call when done attempting to connect. - */ - export function waitForClientReady(client: Client, deadline: Deadline, callback: (error: Error | null) => void): void; - - /** - * Close client. - * @param clientObj The client to close - */ - export function closeClient(clientObj: Client): void; -} diff --git a/packages/grpc-native-core/index.js b/packages/grpc-native-core/index.js deleted file mode 100644 index af597f708..000000000 --- a/packages/grpc-native-core/index.js +++ /dev/null @@ -1,255 +0,0 @@ -/** - * @license - * 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. - * - */ - -'use strict'; - -var path = require('path'); -var fs = require('fs'); - -var SSL_ROOTS_PATH = path.resolve(__dirname, 'deps', 'grpc', 'etc', 'roots.pem'); - -var _ = require('lodash'); - -var ProtoBuf = require('protobufjs'); - -var client = require('./src/client.js'); - -var server = require('./src/server.js'); - -var common = require('./src/common.js'); - -var Metadata = require('./src/metadata.js'); - -var grpc = require('./src/grpc_extension'); - -var protobuf_js_5_common = require('./src/protobuf_js_5_common'); -var protobuf_js_6_common = require('./src/protobuf_js_6_common'); - -var constants = require('./src/constants.js'); - -grpc.setDefaultRootsPem(fs.readFileSync(SSL_ROOTS_PATH, 'ascii')); - -/** - * @namespace grpc - */ - -/** - * Load a ProtoBuf.js object as a gRPC object. - * @memberof grpc - * @alias grpc.loadObject - * @param {Object} value The ProtoBuf.js reflection object to load - * @param {Object=} options Options to apply to the loaded file - * @param {bool=} [options.binaryAsBase64=false] deserialize bytes values as - * base64 strings instead of Buffers - * @param {bool=} [options.longsAsStrings=true] deserialize long values as - * strings instead of objects - * @param {bool=} [options.enumsAsStrings=true] deserialize enum values as - * strings instead of numbers. Only works with Protobuf.js 6 values. - * @param {bool=} [options.deprecatedArgumentOrder=false] use the beta method - * argument order for client methods, with optional arguments after the - * callback. This option is only a temporary stopgap measure to smooth an - * API breakage. It is deprecated, and new code should not use it. - * @param {(number|string)=} [options.protobufjsVersion='detect'] 5 and 6 - * respectively indicate that an object from the corresponding version of - * Protobuf.js is provided in the value argument. If the option is 'detect', - * gRPC will guess what the version is based on the structure of the value. - * @return {Object} The resulting gRPC object. - */ -exports.loadObject = function loadObject(value, options) { - options = _.defaults(options, common.defaultGrpcOptions); - options = _.defaults(options, {'protobufjsVersion': 'detect'}); - var protobufjsVersion; - if (options.protobufjsVersion === 'detect') { - if (protobuf_js_6_common.isProbablyProtobufJs6(value)) { - protobufjsVersion = 6; - } else if (protobuf_js_5_common.isProbablyProtobufJs5(value)) { - protobufjsVersion = 5; - } else { - var error_message = 'Could not detect ProtoBuf.js version. Please ' + - 'specify the version number with the "protobufjs_version" option'; - throw new Error(error_message); - } - } else { - protobufjsVersion = options.protobufjsVersion; - } - switch (protobufjsVersion) { - case 6: return protobuf_js_6_common.loadObject(value, options); - case 5: - return protobuf_js_5_common.loadObject(value, options); - default: - throw new Error('Unrecognized protobufjsVersion', protobufjsVersion); - } -}; - -var loadObject = exports.loadObject; - -/** - * Load a gRPC object from a .proto file. - * @memberof grpc - * @alias grpc.load - * @param {string|{root: string, file: string}} filename The file to load - * @param {string=} format The file format to expect. Must be either 'proto' or - * 'json'. Defaults to 'proto' - * @param {Object=} options Options to apply to the loaded file - * @param {bool=} [options.convertFieldsToCamelCase=false] Load this file with - * field names in camel case instead of their original case - * @param {bool=} [options.binaryAsBase64=false] deserialize bytes values as - * base64 strings instead of Buffers - * @param {bool=} [options.longsAsStrings=true] deserialize long values as - * strings instead of objects - * @param {bool=} [options.deprecatedArgumentOrder=false] use the beta method - * argument order for client methods, with optional arguments after the - * callback. This option is only a temporary stopgap measure to smooth an - * API breakage. It is deprecated, and new code should not use it. - * @return {Object} The resulting gRPC object - */ -exports.load = function load(filename, format, options) { - options = _.defaults(options, common.defaultGrpcOptions); - options.protobufjsVersion = 5; - if (!format) { - format = 'proto'; - } - var convertFieldsToCamelCaseOriginal = ProtoBuf.convertFieldsToCamelCase; - if(options && options.hasOwnProperty('convertFieldsToCamelCase')) { - ProtoBuf.convertFieldsToCamelCase = options.convertFieldsToCamelCase; - } - var builder; - try { - switch(format) { - case 'proto': - builder = ProtoBuf.loadProtoFile(filename); - break; - case 'json': - builder = ProtoBuf.loadJsonFile(filename); - break; - default: - throw new Error('Unrecognized format "' + format + '"'); - } - } finally { - ProtoBuf.convertFieldsToCamelCase = convertFieldsToCamelCaseOriginal; - } - return loadObject(builder.ns, options); -}; - -var log_template = _.template( - '{severity} {timestamp}\t{file}:{line}]\t{message}', - {interpolate: /{([\s\S]+?)}/g}); - -/** - * Sets the logger function for the gRPC module. For debugging purposes, the C - * core will log synchronously directly to stdout unless this function is - * called. Note: the output format here is intended to be informational, and - * is not guaranteed to stay the same in the future. - * Logs will be directed to logger.error. - * @memberof grpc - * @alias grpc.setLogger - * @param {Console} logger A Console-like object. - */ -exports.setLogger = function setLogger(logger) { - common.logger = logger; - grpc.setDefaultLoggerCallback(function(file, line, severity, - message, timestamp) { - logger.error(log_template({ - file: path.basename(file), - line: line, - severity: severity, - message: message, - timestamp: timestamp.toISOString() - })); - }); -}; - -/** - * Sets the logger verbosity for gRPC module logging. The options are members - * of the grpc.logVerbosity map. - * @memberof grpc - * @alias grpc.setLogVerbosity - * @param {Number} verbosity The minimum severity to log - */ -exports.setLogVerbosity = function setLogVerbosity(verbosity) { - common.logVerbosity = verbosity; - grpc.setLogVerbosity(verbosity); -}; - -exports.Server = server.Server; - -exports.Metadata = Metadata; - -exports.status = constants.status; - -exports.propagate = constants.propagate; - -exports.callError = constants.callError; - -exports.writeFlags = constants.writeFlags; - -exports.logVerbosity = constants.logVerbosity; - -exports.credentials = require('./src/credentials.js'); - -/** - * ServerCredentials factories - * @constructor ServerCredentials - * @memberof grpc - */ -exports.ServerCredentials = grpc.ServerCredentials; - -/** - * Create insecure server credentials - * @name grpc.ServerCredentials.createInsecure - * @kind function - * @return {grpc.ServerCredentials} - */ - -/** - * A private key and certificate pair - * @typedef {Object} grpc.ServerCredentials~keyCertPair - * @property {Buffer} privateKey The server's private key - * @property {Buffer} certChain The server's certificate chain - */ - -/** - * Create SSL server credentials - * @name grpc.ServerCredentials.createInsecure - * @kind function - * @param {?Buffer} rootCerts Root CA certificates for validating client - * certificates - * @param {Array} keyCertPairs A list of - * private key and certificate chain pairs to be used for authenticating - * the server - * @param {boolean} [checkClientCertificate=false] Indicates that the server - * should request and verify the client's certificates - * @return {grpc.ServerCredentials} - */ - -exports.makeGenericClientConstructor = client.makeClientConstructor; - -exports.getClientChannel = client.getClientChannel; - -exports.waitForClientReady = client.waitForClientReady; - -/** - * @memberof grpc - * @alias grpc.closeClient - * @param {grpc.Client} client_obj The client to close - */ -exports.closeClient = function closeClient(client_obj) { - client.Client.prototype.close.apply(client_obj); -}; - -exports.Client = client.Client; diff --git a/packages/grpc-native-core/jsdoc_conf.json b/packages/grpc-native-core/jsdoc_conf.json deleted file mode 100644 index 92de29900..000000000 --- a/packages/grpc-native-core/jsdoc_conf.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "tags": { - "allowUnknownTags": true - }, - "opts": { - "destination": "docs/gen/native/core" - }, - "plugins": ["plugins/markdown"], - "templates": { - "cleverLinks": false, - "monospaceLinks": false, - "default": { - "outputSourceFiles": true - } - } -} diff --git a/packages/grpc-native-core/package.json b/packages/grpc-native-core/package.json deleted file mode 100644 index f3fb57135..000000000 --- a/packages/grpc-native-core/package.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "name": "grpc", - "version": "1.7.1", - "author": "Google Inc.", - "description": "gRPC Library for Node", - "homepage": "https://grpc.io/", - "repository": { - "type": "git", - "url": "https://github.com/grpc/grpc-node.git" - }, - "bugs": "https://github.com/grpc/grpc-node/issues", - "contributors": [ - { - "name": "Michael Lumish", - "email": "mlumish@google.com" - } - ], - "directories": { - "lib": "src" - }, - "scripts": { - "build": "./node_modules/.bin/node-pre-gyp build", - "electron-build": "./node_modules/.bin/node-pre-gyp configure build --runtime=electron --disturl=https://atom.io/download/atom-shell", - "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha test", - "install": "./node_modules/.bin/node-pre-gyp install --fallback-to-build --library=static_library" - }, - "bundledDependencies": [ - "node-pre-gyp" - ], - "dependencies": { - "arguejs": "^0.2.3", - "lodash": "^4.15.0", - "nan": "^2.0.0", - "node-pre-gyp": "^0.6.39", - "protobufjs": "^5.0.0" - }, - "devDependencies": { - "electron-mocha": "^3.1.1", - "istanbul": "^0.4.4" - }, - "engines": { - "node": ">=4" - }, - "binary": { - "module_name": "grpc_node", - "module_path": "src/node/extension_binary/{node_abi}-{platform}-{arch}", - "host": "https://storage.googleapis.com/", - "remote_path": "grpc-precompiled-binaries/node/{name}/v{version}", - "package_name": "{node_abi}-{platform}-{arch}.tar.gz" - }, - "files": [ - "LICENSE", - "README.md", - "deps/grpc/etc/", - "index.js", - "index.d.ts", - "src/*.js", - "ext/*.{cc,h}", - "deps/grpc/include/grpc/**/*.h", - "deps/grpc/src/core/**/*.{c,h}", - "deps/grpc/src/boringssl/*.{c,h}", - "deps/grpc/third_party/nanopb/*.{c,h}", - "deps/grpc/third_party/zlib/**/*.{c,h}", - "deps/grpc/third_party/boringssl/crypto/**/*.{c,h}", - "deps/grpc/third_party/boringssl/include/**/*.{c,h}", - "deps/grpc/third_party/boringssl/ssl/**/*.{c,h}", - "binding.gyp" - ], - "main": "index.js", - "typings": "index.d.ts", - "license": "Apache-2.0", - "jshintConfig": { - "bitwise": true, - "curly": true, - "eqeqeq": true, - "esnext": true, - "freeze": true, - "immed": true, - "indent": 2, - "latedef": "nofunc", - "maxlen": 80, - "mocha": true, - "newcap": true, - "node": true, - "noarg": true, - "quotmark": "single", - "strict": true, - "trailing": true, - "undef": true, - "unused": "vars" - } -} diff --git a/packages/grpc-native-core/src/client.js b/packages/grpc-native-core/src/client.js deleted file mode 100644 index 24af23b8a..000000000 --- a/packages/grpc-native-core/src/client.js +++ /dev/null @@ -1,957 +0,0 @@ -/** - * @license - * 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. - * - */ - -/** - * Client module - * - * This module contains the factory method for creating Client classes, and the - * method calling code for all types of methods. - * - * @example Create a client and call a method on it - * - * var proto_obj = grpc.load(proto_file_path); - * var Client = proto_obj.package.subpackage.ServiceName; - * var client = new Client(server_address, client_credentials); - * var call = client.unaryMethod(arguments, callback); - */ - -'use strict'; - -var _ = require('lodash'); -var arguejs = require('arguejs'); - -var grpc = require('./grpc_extension'); - -var common = require('./common'); - -var Metadata = require('./metadata'); - -var constants = require('./constants'); - -var EventEmitter = require('events').EventEmitter; - -var stream = require('stream'); - -var Readable = stream.Readable; -var Writable = stream.Writable; -var Duplex = stream.Duplex; -var util = require('util'); -var version = require('../package.json').version; - -/** - * Initial response metadata sent by the server when it starts processing the - * call - * @event grpc~ClientUnaryCall#metadata - * @type {grpc.Metadata} - */ - -/** - * Status of the call when it has completed. - * @event grpc~ClientUnaryCall#status - * @type grpc~StatusObject - */ - -util.inherits(ClientUnaryCall, EventEmitter); - -/** - * An EventEmitter. Used for unary calls. - * @constructor grpc~ClientUnaryCall - * @extends external:EventEmitter - * @param {grpc.internal~Call} call The call object associated with the request - */ -function ClientUnaryCall(call) { - EventEmitter.call(this); - this.call = call; -} - -util.inherits(ClientWritableStream, Writable); - -/** - * A stream that the client can write to. Used for calls that are streaming from - * the client side. - * @constructor grpc~ClientWritableStream - * @extends external:Writable - * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientWritableStream#cancel - * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientWritableStream#getPeer - * @borrows grpc~ClientUnaryCall#event:metadata as - * grpc~ClientWritableStream#metadata - * @borrows grpc~ClientUnaryCall#event:status as - * grpc~ClientWritableStream#status - * @param {grpc.internal~Call} call The call object to send data with - * @param {grpc~serialize=} [serialize=identity] Serialization - * function for writes. - */ -function ClientWritableStream(call, serialize) { - Writable.call(this, {objectMode: true}); - this.call = call; - this.serialize = common.wrapIgnoreNull(serialize); - this.on('finish', function() { - var batch = {}; - batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - call.startBatch(batch, function() {}); - }); -} - -/** - * Write a message to the request stream. If serializing the argument fails, - * the call will be cancelled and the stream will end with an error. - * @name grpc~ClientWritableStream#write - * @kind function - * @override - * @param {*} message The message to write. Must be a valid argument to the - * serialize function of the corresponding method - * @param {grpc.writeFlags} flags Flags to modify how the message is written - * @param {Function} callback Callback for when this chunk of data is flushed - * @return {boolean} As defined for [Writable]{@link external:Writable} - */ - -/** - * Attempt to write the given chunk. Calls the callback when done. This is an - * implementation of a method needed for implementing stream.Writable. - * @private - * @param {*} chunk The chunk to write - * @param {grpc.writeFlags} encoding Used to pass write flags - * @param {function(Error=)} callback Called when the write is complete - */ -function _write(chunk, encoding, callback) { - /* jshint validthis: true */ - var batch = {}; - var message; - var self = this; - if (this.writeFailed) { - /* Once a write fails, just call the callback immediately to let the caller - flush any pending writes. */ - setImmediate(callback); - return; - } - try { - message = this.serialize(chunk); - } catch (e) { - /* Sending this error to the server and emitting it immediately on the - client may put the call in a slightly weird state on the client side, - but passing an object that causes a serialization failure is a misuse - of the API anyway, so that's OK. The primary purpose here is to give the - programmer a useful error and to stop the stream properly */ - this.call.cancelWithStatus(constants.status.INTERNAL, - 'Serialization failure'); - callback(e); - return; - } - if (_.isFinite(encoding)) { - /* Attach the encoding if it is a finite number. This is the closest we - * can get to checking that it is valid flags */ - message.grpcWriteFlags = encoding; - } - batch[grpc.opType.SEND_MESSAGE] = message; - this.call.startBatch(batch, function(err, event) { - if (err) { - /* Assume that the call is complete and that writing failed because a - status was received. In that case, set a flag to discard all future - writes */ - self.writeFailed = true; - } - callback(); - }); -} - -ClientWritableStream.prototype._write = _write; - -util.inherits(ClientReadableStream, Readable); - -/** - * A stream that the client can read from. Used for calls that are streaming - * from the server side. - * @constructor grpc~ClientReadableStream - * @extends external:Readable - * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientReadableStream#cancel - * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientReadableStream#getPeer - * @borrows grpc~ClientUnaryCall#event:metadata as - * grpc~ClientReadableStream#metadata - * @borrows grpc~ClientUnaryCall#event:status as - * grpc~ClientReadableStream#status - * @param {grpc.internal~Call} call The call object to read data with - * @param {grpc~deserialize=} [deserialize=identity] - * Deserialization function for reads - */ -function ClientReadableStream(call, deserialize) { - Readable.call(this, {objectMode: true}); - this.call = call; - this.finished = false; - this.reading = false; - this.deserialize = common.wrapIgnoreNull(deserialize); - /* Status generated from reading messages from the server. Overrides the - * status from the server if not OK */ - this.read_status = null; - /* Status received from the server. */ - this.received_status = null; -} - -/** - * Called when all messages from the server have been processed. The status - * parameter indicates that the call should end with that status. status - * defaults to OK if not provided. - * @param {Object!} status The status that the call should end with - * @private - */ -function _readsDone(status) { - /* jshint validthis: true */ - if (!status) { - status = {code: constants.status.OK, details: 'OK'}; - } - if (status.code !== constants.status.OK) { - this.call.cancelWithStatus(status.code, status.details); - } - this.finished = true; - this.read_status = status; - this._emitStatusIfDone(); -} - -ClientReadableStream.prototype._readsDone = _readsDone; - -/** - * Called to indicate that we have received a status from the server. - * @private - */ -function _receiveStatus(status) { - /* jshint validthis: true */ - this.received_status = status; - this._emitStatusIfDone(); -} - -ClientReadableStream.prototype._receiveStatus = _receiveStatus; - -/** - * If we have both processed all incoming messages and received the status from - * the server, emit the status. Otherwise, do nothing. - * @private - */ -function _emitStatusIfDone() { - /* jshint validthis: true */ - var status; - if (this.read_status && this.received_status) { - if (this.read_status.code !== constants.status.OK) { - status = this.read_status; - } else { - status = this.received_status; - } - if (status.code === constants.status.OK) { - this.push(null); - } else { - var error = new Error(status.details); - error.code = status.code; - error.metadata = status.metadata; - this.emit('error', error); - } - this.emit('status', status); - } -} - -ClientReadableStream.prototype._emitStatusIfDone = _emitStatusIfDone; - -/** - * Read the next object from the stream. - * @private - * @param {*} size Ignored because we use objectMode=true - */ -function _read(size) { - /* jshint validthis: true */ - var self = this; - /** - * Callback to be called when a READ event is received. Pushes the data onto - * the read queue and starts reading again if applicable - * @param {grpc.Event} event READ event object - */ - function readCallback(err, event) { - if (err) { - // Something has gone wrong. Stop reading and wait for status - self.finished = true; - self._readsDone(); - return; - } - var data = event.read; - var deserialized; - try { - deserialized = self.deserialize(data); - } catch (e) { - self._readsDone({code: constants.status.INTERNAL, - details: 'Failed to parse server response'}); - return; - } - if (data === null) { - self._readsDone(); - return; - } - if (self.push(deserialized) && data !== null) { - var read_batch = {}; - read_batch[grpc.opType.RECV_MESSAGE] = true; - self.call.startBatch(read_batch, readCallback); - } else { - self.reading = false; - } - } - if (self.finished) { - self.push(null); - } else { - if (!self.reading) { - self.reading = true; - var read_batch = {}; - read_batch[grpc.opType.RECV_MESSAGE] = true; - self.call.startBatch(read_batch, readCallback); - } - } -} - -ClientReadableStream.prototype._read = _read; - -util.inherits(ClientDuplexStream, Duplex); - -/** - * A stream that the client can read from or write to. Used for calls with - * duplex streaming. - * @constructor grpc~ClientDuplexStream - * @extends external:Duplex - * @borrows grpc~ClientUnaryCall#cancel as grpc~ClientDuplexStream#cancel - * @borrows grpc~ClientUnaryCall#getPeer as grpc~ClientDuplexStream#getPeer - * @borrows grpc~ClientWritableStream#write as grpc~ClientDuplexStream#write - * @borrows grpc~ClientUnaryCall#event:metadata as - * grpc~ClientDuplexStream#metadata - * @borrows grpc~ClientUnaryCall#event:status as - * grpc~ClientDuplexStream#status - * @param {grpc.internal~Call} call Call object to proxy - * @param {grpc~serialize=} [serialize=identity] Serialization - * function for requests - * @param {grpc~deserialize=} [deserialize=identity] - * Deserialization function for responses - */ -function ClientDuplexStream(call, serialize, deserialize) { - Duplex.call(this, {objectMode: true}); - this.serialize = common.wrapIgnoreNull(serialize); - this.deserialize = common.wrapIgnoreNull(deserialize); - this.call = call; - /* Status generated from reading messages from the server. Overrides the - * status from the server if not OK */ - this.read_status = null; - /* Status received from the server. */ - this.received_status = null; - this.on('finish', function() { - var batch = {}; - batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - call.startBatch(batch, function() {}); - }); -} - -ClientDuplexStream.prototype._readsDone = _readsDone; -ClientDuplexStream.prototype._receiveStatus = _receiveStatus; -ClientDuplexStream.prototype._emitStatusIfDone = _emitStatusIfDone; -ClientDuplexStream.prototype._read = _read; -ClientDuplexStream.prototype._write = _write; - -/** - * Cancel the ongoing call. Results in the call ending with a CANCELLED status, - * unless it has already ended with some other status. - * @alias grpc~ClientUnaryCall#cancel - */ -function cancel() { - /* jshint validthis: true */ - this.call.cancel(); -} - -ClientUnaryCall.prototype.cancel = cancel; -ClientReadableStream.prototype.cancel = cancel; -ClientWritableStream.prototype.cancel = cancel; -ClientDuplexStream.prototype.cancel = cancel; - -/** - * Get the endpoint this call/stream is connected to. - * @return {string} The URI of the endpoint - * @alias grpc~ClientUnaryCall#getPeer - */ -function getPeer() { - /* jshint validthis: true */ - return this.call.getPeer(); -} - -ClientUnaryCall.prototype.getPeer = getPeer; -ClientReadableStream.prototype.getPeer = getPeer; -ClientWritableStream.prototype.getPeer = getPeer; -ClientDuplexStream.prototype.getPeer = getPeer; - -/** - * Any client call type - * @typedef {(grpc~ClientUnaryCall|grpc~ClientReadableStream| - * grpc~ClientWritableStream|grpc~ClientDuplexStream)} - * grpc.Client~Call - */ - -/** - * Options that can be set on a call. - * @typedef {Object} grpc.Client~CallOptions - * @property {grpc~Deadline} deadline The deadline for the entire call to - * complete. - * @property {string} host Server hostname to set on the call. Only meaningful - * if different from the server address used to construct the client. - * @property {grpc.Client~Call} parent Parent call. Used in servers when - * making a call as part of the process of handling a call. Used to - * propagate some information automatically, as specified by - * propagate_flags. - * @property {number} propagate_flags Indicates which properties of a parent - * call should propagate to this call. Bitwise combination of flags in - * {@link grpc.propagate}. - * @property {grpc.credentials~CallCredentials} credentials The credentials that - * should be used to make this particular call. - */ - -/** - * Get a call object built with the provided options. - * @access private - * @param {grpc.Client~CallOptions=} options Options object. - */ -function getCall(channel, method, options) { - var deadline; - var host; - var parent; - var propagate_flags; - var credentials; - if (options) { - deadline = options.deadline; - host = options.host; - parent = _.get(options, 'parent.call'); - propagate_flags = options.propagate_flags; - credentials = options.credentials; - } - if (deadline === undefined) { - deadline = Infinity; - } - var call = new grpc.Call(channel, method, deadline, host, - parent, propagate_flags); - if (credentials) { - call.setCredentials(credentials); - } - return call; -} - -/** - * A generic gRPC client. Primarily useful as a base class for generated clients - * @memberof grpc - * @constructor - * @param {string} address Server address to connect to - * @param {grpc.credentials~ChannelCredentials} credentials Credentials to use to connect to - * the server - * @param {Object} options Options to apply to channel creation - */ -function Client(address, credentials, options) { - if (!options) { - options = {}; - } - /* Append the grpc-node user agent string after the application user agent - * string, and put the combination at the beginning of the user agent string - */ - if (options['grpc.primary_user_agent']) { - options['grpc.primary_user_agent'] += ' '; - } else { - options['grpc.primary_user_agent'] = ''; - } - options['grpc.primary_user_agent'] += 'grpc-node/' + version; - /* Private fields use $ as a prefix instead of _ because it is an invalid - * prefix of a method name */ - this.$channel = new grpc.Channel(address, credentials, options); -} - -exports.Client = Client; - -/** - * @callback grpc.Client~requestCallback - * @param {?grpc~ServiceError} error The error, if the call - * failed - * @param {*} value The response value, if the call succeeded - */ - -/** - * Make a unary request to the given method, using the given serialize - * and deserialize functions, with the given argument. - * @param {string} method The name of the method to request - * @param {grpc~serialize} serialize The serialization function for - * inputs - * @param {grpc~deserialize} deserialize The deserialization - * function for outputs - * @param {*} argument The argument to the call. Should be serializable with - * serialize - * @param {grpc.Metadata=} metadata Metadata to add to the call - * @param {grpc.Client~CallOptions=} options Options map - * @param {grpc.Client~requestCallback} callback The callback to - * for when the response is received - * @return {grpc~ClientUnaryCall} An event emitter for stream related events - */ -Client.prototype.makeUnaryRequest = function(method, serialize, deserialize, - argument, metadata, options, - callback) { - /* While the arguments are listed in the function signature, those variables - * are not used directly. Instead, ArgueJS processes the arguments - * object. This allows for simple handling of optional arguments in the - * middle of the argument list, and also provides type checking. */ - var args = arguejs({method: String, serialize: Function, - deserialize: Function, - argument: null, metadata: [Metadata, new Metadata()], - options: [Object], callback: Function}, arguments); - var call = getCall(this.$channel, method, args.options); - var emitter = new ClientUnaryCall(call); - metadata = args.metadata.clone(); - var client_batch = {}; - var message = serialize(args.argument); - if (args.options) { - message.grpcWriteFlags = args.options.flags; - } - - client_batch[grpc.opType.SEND_INITIAL_METADATA] = - metadata._getCoreRepresentation(); - client_batch[grpc.opType.SEND_MESSAGE] = message; - client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - client_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - client_batch[grpc.opType.RECV_MESSAGE] = true; - client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(client_batch, function(err, response) { - response.status.metadata = Metadata._fromCoreRepresentation( - response.status.metadata); - var status = response.status; - var error; - var deserialized; - emitter.emit('metadata', Metadata._fromCoreRepresentation( - response.metadata)); - if (status.code === constants.status.OK) { - if (err) { - // Got a batch error, but OK status. Something went wrong - args.callback(err); - return; - } else { - try { - deserialized = deserialize(response.read); - } catch (e) { - /* Change status to indicate bad server response. This will result - * in passing an error to the callback */ - status = { - code: constants.status.INTERNAL, - details: 'Failed to parse server response' - }; - } - } - } - if (status.code !== constants.status.OK) { - error = new Error(status.details); - error.code = status.code; - error.metadata = status.metadata; - args.callback(error); - } else { - args.callback(null, deserialized); - } - emitter.emit('status', status); - }); - return emitter; -}; - -/** - * Make a client stream request to the given method, using the given serialize - * and deserialize functions, with the given argument. - * @param {string} method The name of the method to request - * @param {grpc~serialize} serialize The serialization function for - * inputs - * @param {grpc~deserialize} deserialize The deserialization - * function for outputs - * @param {grpc.Metadata=} metadata Array of metadata key/value pairs to add to - * the call - * @param {grpc.Client~CallOptions=} options Options map - * @param {grpc.Client~requestCallback} callback The callback to for when the - * response is received - * @return {grpc~ClientWritableStream} An event emitter for stream related - * events - */ -Client.prototype.makeClientStreamRequest = function(method, serialize, - deserialize, metadata, - options, callback) { - /* While the arguments are listed in the function signature, those variables - * are not used directly. Instead, ArgueJS processes the arguments - * object. This allows for simple handling of optional arguments in the - * middle of the argument list, and also provides type checking. */ - var args = arguejs({method:String, serialize: Function, - deserialize: Function, - metadata: [Metadata, new Metadata()], - options: [Object], callback: Function}, arguments); - var call = getCall(this.$channel, method, args.options); - metadata = args.metadata.clone(); - var stream = new ClientWritableStream(call, serialize); - var metadata_batch = {}; - metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = - metadata._getCoreRepresentation(); - metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - call.startBatch(metadata_batch, function(err, response) { - if (err) { - // The call has stopped for some reason. A non-OK status will arrive - // in the other batch. - return; - } - stream.emit('metadata', Metadata._fromCoreRepresentation( - response.metadata)); - }); - var client_batch = {}; - client_batch[grpc.opType.RECV_MESSAGE] = true; - client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(client_batch, function(err, response) { - response.status.metadata = Metadata._fromCoreRepresentation( - response.status.metadata); - var status = response.status; - var error; - var deserialized; - if (status.code === constants.status.OK) { - if (err) { - // Got a batch error, but OK status. Something went wrong - args.callback(err); - return; - } else { - try { - deserialized = deserialize(response.read); - } catch (e) { - /* Change status to indicate bad server response. This will result - * in passing an error to the callback */ - status = { - code: constants.status.INTERNAL, - details: 'Failed to parse server response' - }; - } - } - } - if (status.code !== constants.status.OK) { - error = new Error(response.status.details); - error.code = status.code; - error.metadata = status.metadata; - args.callback(error); - } else { - args.callback(null, deserialized); - } - stream.emit('status', status); - }); - return stream; -}; - -/** - * Make a server stream request to the given method, with the given serialize - * and deserialize function, using the given argument - * @param {string} method The name of the method to request - * @param {grpc~serialize} serialize The serialization function for inputs - * @param {grpc~deserialize} deserialize The deserialization - * function for outputs - * @param {*} argument The argument to the call. Should be serializable with - * serialize - * @param {grpc.Metadata=} metadata Array of metadata key/value pairs to add to - * the call - * @param {grpc.Client~CallOptions=} options Options map - * @return {grpc~ClientReadableStream} An event emitter for stream related - * events - */ -Client.prototype.makeServerStreamRequest = function(method, serialize, - deserialize, argument, - metadata, options) { - /* While the arguments are listed in the function signature, those variables - * are not used directly. Instead, ArgueJS processes the arguments - * object. */ - var args = arguejs({method:String, serialize: Function, - deserialize: Function, - argument: null, metadata: [Metadata, new Metadata()], - options: [Object]}, arguments); - var call = getCall(this.$channel, method, args.options); - metadata = args.metadata.clone(); - var stream = new ClientReadableStream(call, deserialize); - var start_batch = {}; - var message = serialize(args.argument); - if (args.options) { - message.grpcWriteFlags = args.options.flags; - } - start_batch[grpc.opType.SEND_INITIAL_METADATA] = - metadata._getCoreRepresentation(); - start_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - start_batch[grpc.opType.SEND_MESSAGE] = message; - start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - call.startBatch(start_batch, function(err, response) { - if (err) { - // The call has stopped for some reason. A non-OK status will arrive - // in the other batch. - return; - } - stream.emit('metadata', Metadata._fromCoreRepresentation( - response.metadata)); - }); - var status_batch = {}; - status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(status_batch, function(err, response) { - if (err) { - stream.emit('error', err); - return; - } - response.status.metadata = Metadata._fromCoreRepresentation( - response.status.metadata); - stream._receiveStatus(response.status); - }); - return stream; -}; - - -/** - * Make a bidirectional stream request with this method on the given channel. - * @param {string} method The name of the method to request - * @param {grpc~serialize} serialize The serialization function for inputs - * @param {grpc~deserialize} deserialize The deserialization - * function for outputs - * @param {grpc.Metadata=} metadata Array of metadata key/value - * pairs to add to the call - * @param {grpc.Client~CallOptions=} options Options map - * @return {grpc~ClientDuplexStream} An event emitter for stream related events - */ -Client.prototype.makeBidiStreamRequest = function(method, serialize, - deserialize, metadata, - options) { - /* While the arguments are listed in the function signature, those variables - * are not used directly. Instead, ArgueJS processes the arguments - * object. */ - var args = arguejs({method:String, serialize: Function, - deserialize: Function, - metadata: [Metadata, new Metadata()], - options: [Object]}, arguments); - var call = getCall(this.$channel, method, args.options); - metadata = args.metadata.clone(); - var stream = new ClientDuplexStream(call, serialize, deserialize); - var start_batch = {}; - start_batch[grpc.opType.SEND_INITIAL_METADATA] = - metadata._getCoreRepresentation(); - start_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - call.startBatch(start_batch, function(err, response) { - if (err) { - // The call has stopped for some reason. A non-OK status will arrive - // in the other batch. - return; - } - stream.emit('metadata', Metadata._fromCoreRepresentation( - response.metadata)); - }); - var status_batch = {}; - status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(status_batch, function(err, response) { - if (err) { - stream.emit('error', err); - return; - } - response.status.metadata = Metadata._fromCoreRepresentation( - response.status.metadata); - stream._receiveStatus(response.status); - }); - return stream; -}; - -/** - * Close this client. - */ -Client.prototype.close = function() { - this.$channel.close(); -}; - -/** - * Return the underlying channel object for the specified client - * @return {Channel} The channel - */ -Client.prototype.getChannel = function() { - return this.$channel; -}; - -/** - * Wait for the client to be ready. The callback will be called when the - * client has successfully connected to the server, and it will be called - * with an error if the attempt to connect to the server has unrecoverablly - * failed or if the deadline expires. This function will make the channel - * start connecting if it has not already done so. - * @param {grpc~Deadline} deadline When to stop waiting for a connection. - * @param {function(Error)} callback The callback to call when done attempting - * to connect. - */ -Client.prototype.waitForReady = function(deadline, callback) { - var self = this; - var checkState = function(err) { - if (err) { - callback(new Error('Failed to connect before the deadline')); - return; - } - var new_state = self.$channel.getConnectivityState(true); - if (new_state === grpc.connectivityState.READY) { - callback(); - } else if (new_state === grpc.connectivityState.FATAL_FAILURE) { - callback(new Error('Failed to connect to server')); - } else { - self.$channel.watchConnectivityState(new_state, deadline, checkState); - } - }; - /* Force a single round of polling to ensure that the channel state is up - * to date */ - grpc.forcePoll(); - setImmediate(checkState); -}; - -/** - * Map with short names for each of the requester maker functions. Used in - * makeClientConstructor - * @private - */ -var requester_funcs = { - unary: Client.prototype.makeUnaryRequest, - server_stream: Client.prototype.makeServerStreamRequest, - client_stream: Client.prototype.makeClientStreamRequest, - bidi: Client.prototype.makeBidiStreamRequest -}; - -function getDefaultValues(metadata, options) { - var res = {}; - res.metadata = metadata || new Metadata(); - res.options = options || {}; - return res; -} - -/** - * Map with wrappers for each type of requester function to make it use the old - * argument order with optional arguments after the callback. - * @access private - */ -var deprecated_request_wrap = { - unary: function(makeUnaryRequest) { - return function makeWrappedUnaryRequest(argument, callback, - metadata, options) { - /* jshint validthis: true */ - var opt_args = getDefaultValues(metadata, metadata); - return makeUnaryRequest.call(this, argument, opt_args.metadata, - opt_args.options, callback); - }; - }, - client_stream: function(makeServerStreamRequest) { - return function makeWrappedClientStreamRequest(callback, metadata, - options) { - /* jshint validthis: true */ - var opt_args = getDefaultValues(metadata, options); - return makeServerStreamRequest.call(this, opt_args.metadata, - opt_args.options, callback); - }; - }, - server_stream: _.identity, - bidi: _.identity -}; - -/** - * Creates a constructor for a client with the given methods, as specified in - * the methods argument. The resulting class will have an instance method for - * each method in the service, which is a partial application of one of the - * [Client]{@link grpc.Client} request methods, depending on `requestSerialize` - * and `responseSerialize`, with the `method`, `serialize`, and `deserialize` - * arguments predefined. - * @memberof grpc - * @alias grpc~makeGenericClientConstructor - * @param {grpc~ServiceDefinition} methods An object mapping method names to - * method attributes - * @param {string} serviceName The fully qualified name of the service - * @param {Object} class_options An options object. - * @param {boolean=} [class_options.deprecatedArgumentOrder=false] Indicates - * that the old argument order should be used for methods, with optional - * arguments at the end instead of the callback at the end. This option - * is only a temporary stopgap measure to smooth an API breakage. - * It is deprecated, and new code should not use it. - * @return {function} New client constructor, which is a subclass of - * {@link grpc.Client}, and has the same arguments as that constructor. - */ -exports.makeClientConstructor = function(methods, serviceName, - class_options) { - if (!class_options) { - class_options = {}; - } - - function ServiceClient(address, credentials, options) { - Client.call(this, address, credentials, options); - } - - util.inherits(ServiceClient, Client); - - _.each(methods, function(attrs, name) { - var method_type; - if (_.startsWith(name, '$')) { - throw new Error('Method names cannot start with $'); - } - if (attrs.requestStream) { - if (attrs.responseStream) { - method_type = 'bidi'; - } else { - method_type = 'client_stream'; - } - } else { - if (attrs.responseStream) { - method_type = 'server_stream'; - } else { - method_type = 'unary'; - } - } - var serialize = attrs.requestSerialize; - var deserialize = attrs.responseDeserialize; - var method_func = _.partial(requester_funcs[method_type], attrs.path, - serialize, deserialize); - if (class_options.deprecatedArgumentOrder) { - ServiceClient.prototype[name] = deprecated_request_wrap(method_func); - } else { - ServiceClient.prototype[name] = method_func; - } - // Associate all provided attributes with the method - _.assign(ServiceClient.prototype[name], attrs); - if (attrs.originalName) { - ServiceClient.prototype[attrs.originalName] = ServiceClient.prototype[name]; - } - }); - - ServiceClient.service = methods; - - return ServiceClient; -}; - -/** - * Return the underlying channel object for the specified client - * @memberof grpc - * @alias grpc~getClientChannel - * @param {grpc.Client} client The client - * @return {Channel} The channel - * @see grpc.Client#getChannel - */ -exports.getClientChannel = function(client) { - return Client.prototype.getChannel.call(client); -}; - -/** - * Wait for the client to be ready. The callback will be called when the - * client has successfully connected to the server, and it will be called - * with an error if the attempt to connect to the server has unrecoverablly - * failed or if the deadline expires. This function will make the channel - * start connecting if it has not already done so. - * @memberof grpc - * @alias grpc~waitForClientReady - * @param {grpc.Client} client The client to wait on - * @param {grpc~Deadline} deadline When to stop waiting for a connection. Pass - * Infinity to wait forever. - * @param {function(Error)} callback The callback to call when done attempting - * to connect. - * @see grpc.Client#waitForReady - */ -exports.waitForClientReady = function(client, deadline, callback) { - Client.prototype.waitForReady.call(client, deadline, callback); -}; diff --git a/packages/grpc-native-core/src/common.js b/packages/grpc-native-core/src/common.js deleted file mode 100644 index 5882cf1c6..000000000 --- a/packages/grpc-native-core/src/common.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * @license - * 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. - * - */ - -'use strict'; - -var _ = require('lodash'); - -/** - * Wrap a function to pass null-like values through without calling it. If no - * function is given, just uses the identity. - * @private - * @param {?function} func The function to wrap - * @return {function} The wrapped function - */ -exports.wrapIgnoreNull = function wrapIgnoreNull(func) { - if (!func) { - return _.identity; - } - return function(arg) { - if (arg === null || arg === undefined) { - return null; - } - return func(arg); - }; -}; - -/** - * The logger object for the gRPC module. Defaults to console. - * @private - */ -exports.logger = console; - -/** - * The current logging verbosity. 0 corresponds to logging everything - * @private - */ -exports.logVerbosity = 0; - -/** - * Log a message if the severity is at least as high as the current verbosity - * @private - * @param {Number} severity A value of the grpc.logVerbosity map - * @param {String} message The message to log - */ -exports.log = function log(severity, message) { - if (severity >= exports.logVerbosity) { - exports.logger.error(message); - } -}; - -/** - * Default options for loading proto files into gRPC - * @alias grpc~defaultLoadOptions - */ -exports.defaultGrpcOptions = { - convertFieldsToCamelCase: false, - binaryAsBase64: false, - longsAsStrings: true, - enumsAsStrings: true, - deprecatedArgumentOrder: false -}; - -// JSDoc definitions that are used in multiple other modules - -/** - * Represents the status of a completed request. If `code` is - * {@link grpc.status}.OK, then the request has completed successfully. - * Otherwise, the request has failed, `details` will contain a description of - * the error. Either way, `metadata` contains the trailing response metadata - * sent by the server when it finishes processing the call. - * @typedef {object} grpc~StatusObject - * @property {number} code The error code, a key of {@link grpc.status} - * @property {string} details Human-readable description of the status - * @property {grpc.Metadata} metadata Trailing metadata sent with the status, - * if applicable - */ - -/** - * Describes how a request has failed. The member `message` will be the same as - * `details` in {@link grpc~StatusObject}, and `code` and `metadata` are the - * same as in that object. - * @typedef {Error} grpc~ServiceError - * @property {number} code The error code, a key of {@link grpc.status} that is - * not `grpc.status.OK` - * @property {grpc.Metadata} metadata Trailing metadata sent with the status, - * if applicable - */ - -/** - * The EventEmitter class in the event standard module - * @external EventEmitter - * @see https://nodejs.org/api/events.html#events_class_eventemitter - */ - -/** - * The Readable class in the stream standard module - * @external Readable - * @see https://nodejs.org/api/stream.html#stream_readable_streams - */ - -/** - * The Writable class in the stream standard module - * @external Writable - * @see https://nodejs.org/api/stream.html#stream_writable_streams - */ - -/** - * The Duplex class in the stream standard module - * @external Duplex - * @see https://nodejs.org/api/stream.html#stream_class_stream_duplex - */ - -/** - * A serialization function - * @callback grpc~serialize - * @param {*} value The value to serialize - * @return {Buffer} The value serialized as a byte sequence - */ - -/** - * A deserialization function - * @callback grpc~deserialize - * @param {Buffer} data The byte sequence to deserialize - * @return {*} The data deserialized as a value - */ - -/** - * The deadline of an operation. If it is a date, the deadline is reached at - * the date and time specified. If it is a finite number, it is treated as - * a number of milliseconds since the Unix Epoch. If it is Infinity, the - * deadline will never be reached. If it is -Infinity, the deadline has already - * passed. - * @typedef {(number|Date)} grpc~Deadline - */ - -/** - * An object that completely defines a service method signature. - * @typedef {Object} grpc~MethodDefinition - * @property {string} path The method's URL path - * @property {boolean} requestStream Indicates whether the method accepts - * a stream of requests - * @property {boolean} responseStream Indicates whether the method returns - * a stream of responses - * @property {grpc~serialize} requestSerialize Serialization - * function for request values - * @property {grpc~serialize} responseSerialize Serialization - * function for response values - * @property {grpc~deserialize} requestDeserialize Deserialization - * function for request data - * @property {grpc~deserialize} responseDeserialize Deserialization - * function for repsonse data - */ - -/** - * An object that completely defines a service. - * @typedef {Object.} grpc~ServiceDefinition - */ diff --git a/packages/grpc-native-core/src/constants.js b/packages/grpc-native-core/src/constants.js deleted file mode 100644 index b91d4214a..000000000 --- a/packages/grpc-native-core/src/constants.js +++ /dev/null @@ -1,237 +0,0 @@ -/** - * @license - * Copyright 2017 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. - * - */ - -/* The comments about status codes are copied verbatim (with some formatting - * modifications) from include/grpc/impl/codegen/status.h, for the purpose of - * including them in generated documentation. - */ -/** - * Enum of status codes that gRPC can return - * @memberof grpc - * @alias grpc.status - * @readonly - * @enum {number} - */ -exports.status = { - /** Not an error; returned on success */ - OK: 0, - /** The operation was cancelled (typically by the caller). */ - 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. - */ - UNKNOWN: 2, - /** - * Client specified an invalid argument. Note that this differs - * from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments - * that are problematic regardless of the state of the system - * (e.g., a malformed file name). - */ - INVALID_ARGUMENT: 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. - */ - DEADLINE_EXCEEDED: 4, - /** Some requested entity (e.g., file or directory) was not found. */ - NOT_FOUND: 5, - /** - * Some entity that we attempted to create (e.g., file or directory) - * already exists. - */ - ALREADY_EXISTS: 6, - /** - * The caller does not have permission to execute the specified - * operation. PERMISSION_DENIED must not be used for rejections - * caused by exhausting some resource (use RESOURCE_EXHAUSTED - * instead for those errors). PERMISSION_DENIED must not be - * used if the caller can not be identified (use UNAUTHENTICATED - * instead for those errors). - */ - PERMISSION_DENIED: 7, - /** - * Some resource has been exhausted, perhaps a per-user quota, or - * perhaps the entire file system is out of space. - */ - RESOURCE_EXHAUSTED: 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 FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: - * - * - Use UNAVAILABLE if the client can retry just the failing call. - * - Use ABORTED if the client should retry at a higher-level - * (e.g., restarting a read-modify-write sequence). - * - Use FAILED_PRECONDITION 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, FAILED_PRECONDITION - * should be returned since the client should not retry unless - * they have first fixed up the directory by deleting files from it. - * - Use FAILED_PRECONDITION if the client performs conditional - * REST Get/Update/Delete on a resource and the resource on the - * server does not match the condition. E.g., conflicting - * read-modify-write on the same resource. - */ - FAILED_PRECONDITION: 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 FAILED_PRECONDITION, - * ABORTED, and UNAVAILABLE. - */ - ABORTED: 10, - /** - * Operation was attempted past the valid range. E.g., seeking or - * reading past end of file. - * - * Unlike INVALID_ARGUMENT, this error indicates a problem that may - * be fixed if the system state changes. For example, a 32-bit file - * system will generate INVALID_ARGUMENT if asked to read at an - * offset that is not in the range [0,2^32-1], but it will generate - * OUT_OF_RANGE if asked to read from an offset past the current - * file size. - * - * There is a fair bit of overlap between FAILED_PRECONDITION and - * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific - * error) when it applies so that callers who are iterating through - * a space can easily look for an OUT_OF_RANGE error to detect when - * they are done. - */ - OUT_OF_RANGE: 11, - /** Operation is not implemented or not supported/enabled in this service. */ - 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. - */ - 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 FAILED_PRECONDITION, - * ABORTED, and UNAVAILABLE. - */ - UNAVAILABLE: 14, - /** Unrecoverable data loss or corruption. */ - DATA_LOSS: 15, - /** - * The request does not have valid authentication credentials for the - * operation. - */ - UNAUTHENTICATED: 16 -}; - -/* The comments about propagation bit flags are copied from - * include/grpc/impl/codegen/propagation_bits.h for the purpose of including - * them in generated documentation. - */ -/** - * Propagation flags: these can be bitwise or-ed to form the propagation option - * for calls. - * - * Users are encouraged to write propagation masks as deltas from the default. - * i.e. write `grpc.propagate.DEFAULTS & ~grpc.propagate.DEADLINE` to disable - * deadline propagation. - * @memberof grpc - * @alias grpc.propagate - * @enum {number} - */ -exports.propagate = { - DEADLINE: 1, - CENSUS_STATS_CONTEXT: 2, - CENSUS_TRACING_CONTEXT: 4, - CANCELLATION: 8, - DEFAULTS: 65535 -}; - -/* Many of the following comments are copied from - * include/grpc/impl/codegen/grpc_types.h - */ -/** - * Call error constants. Call errors almost always indicate bugs in the gRPC - * library, and these error codes are mainly useful for finding those bugs. - * @memberof grpc - * @readonly - * @enum {number} - */ -const callError = { - OK: 0, - ERROR: 1, - NOT_ON_SERVER: 2, - NOT_ON_CLIENT: 3, - ALREADY_INVOKED: 5, - NOT_INVOKED: 6, - ALREADY_FINISHED: 7, - TOO_MANY_OPERATIONS: 8, - INVALID_FLAGS: 9, - INVALID_METADATA: 10, - INVALID_MESSAGE: 11, - NOT_SERVER_COMPLETION_QUEUE: 12, - BATCH_TOO_BIG: 13, - PAYLOAD_TYPE_MISMATCH: 14 -}; - -exports.callError = callError; - -/** - * Write flags: these can be bitwise or-ed to form write options that modify - * how data is written. - * @memberof grpc - * @alias grpc.writeFlags - * @readonly - * @enum {number} - */ -exports.writeFlags = { - /** - * Hint that the write may be buffered and need not go out on the wire - * immediately. GRPC is free to buffer the message until the next non-buffered - * write, or until writes_done, but it need not buffer completely or at all. - */ - BUFFER_HINT: 1, - /** - * Force compression to be disabled for a particular write - */ - NO_COMPRESS: 2 -}; - -/** - * @memberof grpc - * @alias grpc.logVerbosity - * @readonly - * @enum {number} - */ -exports.logVerbosity = { - DEBUG: 0, - INFO: 1, - ERROR: 2 -}; diff --git a/packages/grpc-native-core/src/credentials.js b/packages/grpc-native-core/src/credentials.js deleted file mode 100644 index a23129668..000000000 --- a/packages/grpc-native-core/src/credentials.js +++ /dev/null @@ -1,207 +0,0 @@ -/** - * @license - * 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. - * - */ - -/** - * Credentials module - * - * This module contains factory methods for two different credential types: - * CallCredentials and ChannelCredentials. ChannelCredentials are things like - * SSL credentials that can be used to secure a connection, and are used to - * construct a Client object. CallCredentials genrally modify metadata, so they - * can be attached to an individual method call. - * - * CallCredentials can be composed with other CallCredentials to create - * CallCredentials. ChannelCredentials can be composed with CallCredentials - * to create ChannelCredentials. No combined credential can have more than - * one ChannelCredentials. - * - * For example, to create a client secured with SSL that uses Google - * default application credentials to authenticate: - * - * @example - * var channel_creds = credentials.createSsl(root_certs); - * (new GoogleAuth()).getApplicationDefault(function(err, credential) { - * var call_creds = credentials.createFromGoogleCredential(credential); - * var combined_creds = credentials.combineChannelCredentials( - * channel_creds, call_creds); - * var client = new Client(address, combined_creds); - * }); - * - * @namespace grpc.credentials - */ - -'use strict'; - -var grpc = require('./grpc_extension'); - -/** - * This cannot be constructed directly. Instead, instances of this class should - * be created using the factory functions in {@link grpc.credentials} - * @constructor grpc.credentials~CallCredentials - */ -var CallCredentials = grpc.CallCredentials; - -/** - * This cannot be constructed directly. Instead, instances of this class should - * be created using the factory functions in {@link grpc.credentials} - * @constructor grpc.credentials~ChannelCredentials - */ -var ChannelCredentials = grpc.ChannelCredentials; - -var Metadata = require('./metadata.js'); - -var common = require('./common.js'); - -var constants = require('./constants'); - -var _ = require('lodash'); - -/** - * @external GoogleCredential - * @see https://github.com/google/google-auth-library-nodejs - */ - -/** - * Create an SSL Credentials object. If using a client-side certificate, both - * the second and third arguments must be passed. - * @memberof grpc.credentials - * @alias grpc.credentials.createSsl - * @kind function - * @param {Buffer=} root_certs The root certificate data - * @param {Buffer=} private_key The client certificate private key, if - * applicable - * @param {Buffer=} cert_chain The client certificate cert chain, if applicable - * @return {grpc.credentials~ChannelCredentials} The SSL Credentials object - */ -exports.createSsl = ChannelCredentials.createSsl; - -/** - * @callback grpc.credentials~metadataCallback - * @param {Error} error The error, if getting metadata failed - * @param {grpc.Metadata} metadata The metadata - */ - -/** - * @callback grpc.credentials~generateMetadata - * @param {Object} params Parameters that can modify metadata generation - * @param {string} params.service_url The URL of the service that the call is - * going to - * @param {grpc.credentials~metadataCallback} callback - */ - -/** - * Create a gRPC credentials object from a metadata generation function. This - * function gets the service URL and a callback as parameters. The error - * passed to the callback can optionally have a 'code' value attached to it, - * which corresponds to a status code that this library uses. - * @memberof grpc.credentials - * @alias grpc.credentials.createFromMetadataGenerator - * @param {grpc.credentials~generateMetadata} metadata_generator The function - * that generates metadata - * @return {grpc.credentials~CallCredentials} The credentials object - */ -exports.createFromMetadataGenerator = function(metadata_generator) { - return CallCredentials.createFromPlugin(function(service_url, cb_data, - callback) { - metadata_generator({service_url: service_url}, function(error, metadata) { - var code = constants.status.OK; - var message = ''; - if (error) { - message = error.message; - if (error.hasOwnProperty('code') && _.isFinite(error.code)) { - code = error.code; - } else { - code = constants.status.UNAUTHENTICATED; - } - if (!metadata) { - metadata = new Metadata(); - } - } - callback(code, message, metadata._getCoreRepresentation(), cb_data); - }); - }); -}; - -/** - * Create a gRPC credential from a Google credential object. - * @memberof grpc.credentials - * @alias grpc.credentials.createFromGoogleCredential - * @param {external:GoogleCredential} google_credential The Google credential - * object to use - * @return {grpc.credentials~CallCredentials} The resulting credentials object - */ -exports.createFromGoogleCredential = function(google_credential) { - return exports.createFromMetadataGenerator(function(auth_context, callback) { - var service_url = auth_context.service_url; - google_credential.getRequestMetadata(service_url, function(err, header) { - if (err) { - common.log(constants.logVerbosity.INFO, 'Auth error:' + err); - callback(err); - return; - } - var metadata = new Metadata(); - metadata.add('authorization', header.Authorization); - callback(null, metadata); - }); - }); -}; - -/** - * Combine a ChannelCredentials with any number of CallCredentials into a single - * ChannelCredentials object. - * @memberof grpc.credentials - * @alias grpc.credentials.combineChannelCredentials - * @param {grpc.credentials~ChannelCredentials} channel_credential The ChannelCredentials to - * start with - * @param {...grpc.credentials~CallCredentials} credentials The CallCredentials to compose - * @return {grpc.credentials~ChannelCredentials} A credentials object that combines all of the - * input credentials - */ -exports.combineChannelCredentials = function(channel_credential) { - var current = channel_credential; - for (var i = 1; i < arguments.length; i++) { - current = current.compose(arguments[i]); - } - return current; -}; - -/** - * Combine any number of CallCredentials into a single CallCredentials object - * @memberof grpc.credentials - * @alias grpc.credentials.combineCallCredentials - * @param {...grpc.credentials~CallCredentials} credentials The CallCredentials to compose - * @return {grpc.credentials~CallCredentials} A credentials object that combines all of the input - * credentials - */ -exports.combineCallCredentials = function() { - var current = arguments[0]; - for (var i = 1; i < arguments.length; i++) { - current = current.compose(arguments[i]); - } - return current; -}; - -/** - * Create an insecure credentials object. This is used to create a channel that - * does not use SSL. This cannot be composed with anything. - * @memberof grpc.credentials - * @alias grpc.credentials.createInsecure - * @kind function - * @return {grpc.credentials~ChannelCredentials} The insecure credentials object - */ -exports.createInsecure = ChannelCredentials.createInsecure; diff --git a/packages/grpc-native-core/src/metadata.js b/packages/grpc-native-core/src/metadata.js deleted file mode 100644 index 63bbed76d..000000000 --- a/packages/grpc-native-core/src/metadata.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * @license - * 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. - * - */ - -'use strict'; - -var _ = require('lodash'); - -var grpc = require('./grpc_extension'); - -/** - * Class for storing metadata. Keys are normalized to lowercase ASCII. - * @memberof grpc - * @constructor - * @example - * var metadata = new metadata_module.Metadata(); - * metadata.set('key1', 'value1'); - * metadata.add('key1', 'value2'); - * metadata.get('key1') // returns ['value1', 'value2'] - */ -function Metadata() { - this._internal_repr = {}; -} - -function normalizeKey(key) { - key = key.toLowerCase(); - if (grpc.metadataKeyIsLegal(key)) { - return key; - } else { - throw new Error('Metadata key"' + key + '" contains illegal characters'); - } -} - -function validate(key, value) { - if (grpc.metadataKeyIsBinary(key)) { - if (!(value instanceof Buffer)) { - throw new Error('keys that end with \'-bin\' must have Buffer values'); - } - } else { - if (!_.isString(value)) { - throw new Error( - 'keys that don\'t end with \'-bin\' must have String values'); - } - if (!grpc.metadataNonbinValueIsLegal(value)) { - throw new Error('Metadata string value "' + value + - '" contains illegal characters'); - } - } -} - -/** - * Sets the given value for the given key, replacing any other values associated - * with that key. Normalizes the key. - * @param {String} key The key to set - * @param {String|Buffer} value The value to set. Must be a buffer if and only - * if the normalized key ends with '-bin' - */ -Metadata.prototype.set = function(key, value) { - key = normalizeKey(key); - validate(key, value); - this._internal_repr[key] = [value]; -}; - -/** - * Adds the given value for the given key. Normalizes the key. - * @param {String} key The key to add to. - * @param {String|Buffer} value The value to add. Must be a buffer if and only - * if the normalized key ends with '-bin' - */ -Metadata.prototype.add = function(key, value) { - key = normalizeKey(key); - validate(key, value); - if (!this._internal_repr[key]) { - this._internal_repr[key] = []; - } - this._internal_repr[key].push(value); -}; - -/** - * Remove the given key and any associated values. Normalizes the key. - * @param {String} key The key to remove - */ -Metadata.prototype.remove = function(key) { - key = normalizeKey(key); - if (Object.prototype.hasOwnProperty.call(this._internal_repr, key)) { - delete this._internal_repr[key]; - } -}; - -/** - * Gets a list of all values associated with the key. Normalizes the key. - * @param {String} key The key to get - * @return {Array.} The values associated with that key - */ -Metadata.prototype.get = function(key) { - key = normalizeKey(key); - if (Object.prototype.hasOwnProperty.call(this._internal_repr, key)) { - return this._internal_repr[key]; - } else { - return []; - } -}; - -/** - * Get a map of each key to a single associated value. This reflects the most - * common way that people will want to see metadata. - * @return {Object.} A key/value mapping of the metadata - */ -Metadata.prototype.getMap = function() { - var result = {}; - _.forOwn(this._internal_repr, function(values, key) { - if(values.length > 0) { - result[key] = values[0]; - } - }); - return result; -}; - -/** - * Clone the metadata object. - * @return {grpc.Metadata} The new cloned object - */ -Metadata.prototype.clone = function() { - var copy = new Metadata(); - _.forOwn(this._internal_repr, function(value, key) { - copy._internal_repr[key] = _.clone(value); - }); - return copy; -}; - -/** - * Gets the metadata in the format used by interal code. Intended for internal - * use only. API stability is not guaranteed. - * @private - * @return {Object.>} The metadata - */ -Metadata.prototype._getCoreRepresentation = function() { - return this._internal_repr; -}; - -/** - * Creates a Metadata object from a metadata map in the internal format. - * Intended for internal use only. API stability is not guaranteed. - * @private - * @param {Object.>} The metadata - * @return {Metadata} The new Metadata object - */ -Metadata._fromCoreRepresentation = function(metadata) { - var newMetadata = new Metadata(); - if (metadata) { - _.forOwn(metadata, function(value, key) { - newMetadata._internal_repr[key] = _.clone(value); - }); - } - return newMetadata; -}; - -module.exports = Metadata; diff --git a/packages/grpc-native-core/src/protobuf_js_5_common.js b/packages/grpc-native-core/src/protobuf_js_5_common.js deleted file mode 100644 index 541965fd0..000000000 --- a/packages/grpc-native-core/src/protobuf_js_5_common.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * @license - * Copyright 2017 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. - * - */ - -/** - * @module - * @private - */ - -'use strict'; - -var _ = require('lodash'); -var client = require('./client'); - -/** - * Get a function that deserializes a specific type of protobuf. - * @param {function()} cls The constructor of the message type to deserialize - * @param {bool=} binaryAsBase64 Deserialize bytes fields as base64 strings - * instead of Buffers. Defaults to false - * @param {bool=} longsAsStrings Deserialize long values as strings instead of - * objects. Defaults to true - * @return {function(Buffer):cls} The deserialization function - */ -exports.deserializeCls = function deserializeCls(cls, options) { - /** - * Deserialize a buffer to a message object - * @param {Buffer} arg_buf The buffer to deserialize - * @return {cls} The resulting object - */ - return function deserialize(arg_buf) { - // Convert to a native object with binary fields as Buffers (first argument) - // and longs as strings (second argument) - return cls.decode(arg_buf).toRaw(options.binaryAsBase64, - options.longsAsStrings); - }; -}; - -var deserializeCls = exports.deserializeCls; - -/** - * Get a function that serializes objects to a buffer by protobuf class. - * @param {function()} Cls The constructor of the message type to serialize - * @return {function(Cls):Buffer} The serialization function - */ -exports.serializeCls = function serializeCls(Cls) { - /** - * Serialize an object to a Buffer - * @param {Object} arg The object to serialize - * @return {Buffer} The serialized object - */ - return function serialize(arg) { - return new Buffer(new Cls(arg).encode().toBuffer()); - }; -}; - -var serializeCls = exports.serializeCls; - -/** - * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value. - * @param {ProtoBuf.Reflect.Namespace} value The value to get the name of - * @return {string} The fully qualified name of the value - */ -exports.fullyQualifiedName = function fullyQualifiedName(value) { - if (value === null || value === undefined) { - return ''; - } - var name = value.name; - var parent_name = fullyQualifiedName(value.parent); - if (parent_name !== '') { - name = parent_name + '.' + name; - } - return name; -}; - -var fullyQualifiedName = exports.fullyQualifiedName; - -/** - * Return a map from method names to method attributes for the service. - * @param {ProtoBuf.Reflect.Service} service The service to get attributes for - * @param {Object=} options Options to apply to these attributes - * @return {Object} The attributes map - */ -exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service, - options) { - var prefix = '/' + fullyQualifiedName(service) + '/'; - var binaryAsBase64, longsAsStrings; - if (options) { - binaryAsBase64 = options.binaryAsBase64; - longsAsStrings = options.longsAsStrings; - } - /* This slightly awkward construction is used to make sure we only use - lodash@3.10.1-compatible functions. A previous version used - _.fromPairs, which would be cleaner, but was introduced in lodash - version 4 */ - return _.zipObject(_.map(service.children, function(method) { - return _.camelCase(method.name); - }), _.map(service.children, function(method) { - return { - originalName: method.name, - path: prefix + method.name, - requestStream: method.requestStream, - responseStream: method.responseStream, - requestType: method.resolvedRequestType, - responseType: method.resolvedResponseType, - requestSerialize: serializeCls(method.resolvedRequestType.build()), - requestDeserialize: deserializeCls(method.resolvedRequestType.build(), - options), - responseSerialize: serializeCls(method.resolvedResponseType.build()), - responseDeserialize: deserializeCls(method.resolvedResponseType.build(), - options) - }; - })); -}; - -var getProtobufServiceAttrs = exports.getProtobufServiceAttrs; - -/** - * Load a gRPC object from an existing ProtoBuf.Reflect object. - * @param {ProtoBuf.Reflect.Namespace} value The ProtoBuf object to load. - * @param {Object=} options Options to apply to the loaded object - * @return {Object} The resulting gRPC object - */ -exports.loadObject = function loadObject(value, options) { - var result = {}; - if (!value) { - return value; - } - if (value.hasOwnProperty('ns')) { - return loadObject(value.ns, options); - } - if (value.className === 'Namespace') { - _.each(value.children, function(child) { - result[child.name] = loadObject(child, options); - }); - return result; - } else if (value.className === 'Service') { - return client.makeClientConstructor(getProtobufServiceAttrs(value, options), - options); - } else if (value.className === 'Message' || value.className === 'Enum') { - return value.build(); - } else { - return value; - } -}; - -/** - * The primary purpose of this method is to distinguish between reflection - * objects from different versions of ProtoBuf.js. This is just a heuristic, - * checking for properties that are (currently) specific to this version of - * ProtoBuf.js - * @param {Object} obj The object to check - * @return {boolean} Whether the object appears to be a Protobuf.js 5 - * ReflectionObject - */ -exports.isProbablyProtobufJs5 = function isProbablyProtobufJs5(obj) { - return _.isArray(obj.children) && (typeof obj.build === 'function'); -}; diff --git a/packages/grpc-native-core/src/protobuf_js_6_common.js b/packages/grpc-native-core/src/protobuf_js_6_common.js deleted file mode 100644 index 0f0725167..000000000 --- a/packages/grpc-native-core/src/protobuf_js_6_common.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * @license - * Copyright 2017 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. - * - */ - -/** - * @module - * @private - */ - -'use strict'; - -var _ = require('lodash'); -var client = require('./client'); - -/** - * Get a function that deserializes a specific type of protobuf. - * @param {function()} cls The constructor of the message type to deserialize - * @param {bool=} binaryAsBase64 Deserialize bytes fields as base64 strings - * instead of Buffers. Defaults to false - * @param {bool=} longsAsStrings Deserialize long values as strings instead of - * objects. Defaults to true - * @return {function(Buffer):cls} The deserialization function - */ -exports.deserializeCls = function deserializeCls(cls, options) { - var conversion_options = { - defaults: true, - bytes: options.binaryAsBase64 ? String : Buffer, - longs: options.longsAsStrings ? String : null, - enums: options.enumsAsStrings ? String : null, - oneofs: true - }; - /** - * Deserialize a buffer to a message object - * @param {Buffer} arg_buf The buffer to deserialize - * @return {cls} The resulting object - */ - return function deserialize(arg_buf) { - return cls.toObject(cls.decode(arg_buf), conversion_options); - }; -}; - -var deserializeCls = exports.deserializeCls; - -/** - * Get a function that serializes objects to a buffer by protobuf class. - * @param {function()} Cls The constructor of the message type to serialize - * @return {function(Cls):Buffer} The serialization function - */ -exports.serializeCls = function serializeCls(cls) { - /** - * Serialize an object to a Buffer - * @param {Object} arg The object to serialize - * @return {Buffer} The serialized object - */ - return function serialize(arg) { - var message = cls.fromObject(arg); - return cls.encode(message).finish(); - }; -}; - -var serializeCls = exports.serializeCls; - -/** - * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value. - * @param {ProtoBuf.ReflectionObject} value The value to get the name of - * @return {string} The fully qualified name of the value - */ -exports.fullyQualifiedName = function fullyQualifiedName(value) { - if (value === null || value === undefined) { - return ''; - } - var name = value.name; - var parent_fqn = fullyQualifiedName(value.parent); - if (parent_fqn !== '') { - name = parent_fqn + '.' + name; - } - return name; -}; - -var fullyQualifiedName = exports.fullyQualifiedName; - -/** - * Return a map from method names to method attributes for the service. - * @param {ProtoBuf.Service} service The service to get attributes for - * @param {Object=} options Options to apply to these attributes - * @return {Object} The attributes map - */ -exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service, - options) { - var prefix = '/' + fullyQualifiedName(service) + '/'; - service.resolveAll(); - return _.zipObject(_.map(service.methods, function(method) { - return _.camelCase(method.name); - }), _.map(service.methods, function(method) { - return { - originalName: method.name, - path: prefix + method.name, - requestStream: !!method.requestStream, - responseStream: !!method.responseStream, - requestType: method.resolvedRequestType, - responseType: method.resolvedResponseType, - requestSerialize: serializeCls(method.resolvedRequestType), - requestDeserialize: deserializeCls(method.resolvedRequestType, options), - responseSerialize: serializeCls(method.resolvedResponseType), - responseDeserialize: deserializeCls(method.resolvedResponseType, options) - }; - })); -}; - -var getProtobufServiceAttrs = exports.getProtobufServiceAttrs; - -exports.loadObject = function loadObject(value, options) { - var result = {}; - if (!value) { - return value; - } - if (value.hasOwnProperty('methods')) { - // It's a service object - var service_attrs = getProtobufServiceAttrs(value, options); - return client.makeClientConstructor(service_attrs); - } - - if (value.hasOwnProperty('nested')) { - // It's a namespace or root object - _.each(value.nested, function(nested, name) { - result[name] = loadObject(nested, options); - }); - return result; - } - - // Otherwise, it's not something we need to change - return value; -}; - -/** - * The primary purpose of this method is to distinguish between reflection - * objects from different versions of ProtoBuf.js. This is just a heuristic, - * checking for properties that are (currently) specific to this version of - * ProtoBuf.js - * @param {Object} obj The object to check - * @return {boolean} Whether the object appears to be a Protobuf.js 6 - * ReflectionObject - */ -exports.isProbablyProtobufJs6 = function isProbablyProtobufJs6(obj) { - return (typeof obj.root === 'object') && (typeof obj.resolve === 'function'); -}; diff --git a/packages/grpc-native-core/src/server.js b/packages/grpc-native-core/src/server.js deleted file mode 100644 index cee852e9a..000000000 --- a/packages/grpc-native-core/src/server.js +++ /dev/null @@ -1,965 +0,0 @@ -/** - * @license - * 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. - * - */ - -'use strict'; - -var _ = require('lodash'); - -var grpc = require('./grpc_extension'); - -var common = require('./common'); - -var Metadata = require('./metadata'); - -var constants = require('./constants'); - -var stream = require('stream'); - -var Readable = stream.Readable; -var Writable = stream.Writable; -var Duplex = stream.Duplex; -var util = require('util'); - -var EventEmitter = require('events').EventEmitter; - -/** - * Handle an error on a call by sending it as a status - * @private - * @param {grpc.internal~Call} call The call to send the error on - * @param {(Object|Error)} error The error object - */ -function handleError(call, error) { - var statusMetadata = new Metadata(); - var status = { - code: constants.status.UNKNOWN, - details: 'Unknown Error' - }; - if (error.hasOwnProperty('message')) { - status.details = error.message; - } - if (error.hasOwnProperty('code')) { - status.code = error.code; - if (error.hasOwnProperty('details')) { - status.details = error.details; - } - } - if (error.hasOwnProperty('metadata')) { - statusMetadata = error.metadata; - } - status.metadata = statusMetadata._getCoreRepresentation(); - var error_batch = {}; - if (!call.metadataSent) { - error_batch[grpc.opType.SEND_INITIAL_METADATA] = - (new Metadata())._getCoreRepresentation(); - } - error_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status; - call.startBatch(error_batch, function(){}); -} - -/** - * Send a response to a unary or client streaming call. - * @private - * @param {grpc.Call} call The call to respond on - * @param {*} value The value to respond with - * @param {grpc~serialize} serialize Serialization function for the - * response - * @param {grpc.Metadata=} metadata Optional trailing metadata to send with - * status - * @param {number=} [flags=0] Flags for modifying how the message is sent. - */ -function sendUnaryResponse(call, value, serialize, metadata, flags) { - var end_batch = {}; - var statusMetadata = new Metadata(); - var status = { - code: constants.status.OK, - details: 'OK' - }; - if (metadata) { - statusMetadata = metadata; - } - var message; - try { - message = serialize(value); - } catch (e) { - e.code = constants.status.INTERNAL; - handleError(call, e); - return; - } - status.metadata = statusMetadata._getCoreRepresentation(); - if (!call.metadataSent) { - end_batch[grpc.opType.SEND_INITIAL_METADATA] = - (new Metadata())._getCoreRepresentation(); - call.metadataSent = true; - } - message.grpcWriteFlags = flags; - end_batch[grpc.opType.SEND_MESSAGE] = message; - end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status; - call.startBatch(end_batch, function (){}); -} - -/** - * Initialize a writable stream. This is used for both the writable and duplex - * stream constructors. - * @private - * @param {Writable} stream The stream to set up - * @param {function(*):Buffer=} Serialization function for responses - */ -function setUpWritable(stream, serialize) { - stream.finished = false; - stream.status = { - code : constants.status.OK, - details : 'OK', - metadata : new Metadata() - }; - stream.serialize = common.wrapIgnoreNull(serialize); - function sendStatus() { - var batch = {}; - if (!stream.call.metadataSent) { - stream.call.metadataSent = true; - batch[grpc.opType.SEND_INITIAL_METADATA] = - (new Metadata())._getCoreRepresentation(); - } - - if (stream.status.metadata) { - stream.status.metadata = stream.status.metadata._getCoreRepresentation(); - } - batch[grpc.opType.SEND_STATUS_FROM_SERVER] = stream.status; - stream.call.startBatch(batch, function(){}); - } - stream.on('finish', sendStatus); - /** - * Set the pending status to a given error status. If the error does not have - * code or details properties, the code will be set to grpc.status.UNKNOWN - * and the details will be set to 'Unknown Error'. - * @param {Error} err The error object - */ - function setStatus(err) { - var code = constants.status.UNKNOWN; - var details = 'Unknown Error'; - var metadata = new Metadata(); - if (err.hasOwnProperty('message')) { - details = err.message; - } - if (err.hasOwnProperty('code')) { - code = err.code; - if (err.hasOwnProperty('details')) { - details = err.details; - } - } - if (err.hasOwnProperty('metadata')) { - metadata = err.metadata; - } - stream.status = {code: code, details: details, metadata: metadata}; - } - /** - * Terminate the call. This includes indicating that reads are done, draining - * all pending writes, and sending the given error as a status - * @param {Error} err The error object - * @this GrpcServerStream - */ - function terminateCall(err) { - // Drain readable data - setStatus(err); - stream.end(); - } - stream.on('error', terminateCall); - /** - * Override of Writable#end method that allows for sending metadata with a - * success status. - * @param {Metadata=} metadata Metadata to send with the status - */ - stream.end = function(metadata) { - if (metadata) { - stream.status.metadata = metadata; - } - Writable.prototype.end.call(this); - }; -} - -/** - * Initialize a readable stream. This is used for both the readable and duplex - * stream constructors. - * @private - * @param {Readable} stream The stream to initialize - * @param {grpc~deserialize} deserialize Deserialization function for - * incoming data. - */ -function setUpReadable(stream, deserialize) { - stream.deserialize = common.wrapIgnoreNull(deserialize); - stream.finished = false; - stream.reading = false; - - stream.terminate = function() { - stream.finished = true; - stream.on('data', function() {}); - }; - - stream.on('cancelled', function() { - stream.terminate(); - }); -} - -/** - * Emitted when the call has been cancelled. After this has been emitted, the - * call's `cancelled` property will be set to `true`. - * @event grpc~ServerUnaryCall~cancelled - */ - -util.inherits(ServerUnaryCall, EventEmitter); - -/** - * An EventEmitter. Used for unary calls. - * @constructor grpc~ServerUnaryCall - * @extends external:EventEmitter - * @param {grpc.internal~Call} call The call object associated with the request - * @param {grpc.Metadata} metadata The request metadata from the client - */ -function ServerUnaryCall(call, metadata) { - EventEmitter.call(this); - this.call = call; - /** - * Indicates if the call has been cancelled - * @member {boolean} grpc~ServerUnaryCall#cancelled - */ - this.cancelled = false; - /** - * The request metadata from the client - * @member {grpc.Metadata} grpc~ServerUnaryCall#metadata - */ - this.metadata = metadata; - /** - * The request message from the client - * @member {*} grpc~ServerUnaryCall#request - */ - this.request = undefined; -} - -/** - * Emitted when the call has been cancelled. After this has been emitted, the - * call's `cancelled` property will be set to `true`. - * @event grpc~ServerWritableStream~cancelled - */ - -util.inherits(ServerWritableStream, Writable); - -/** - * A stream that the server can write to. Used for calls that are streaming from - * the server side. - * @constructor grpc~ServerWritableStream - * @extends external:Writable - * @borrows grpc~ServerUnaryCall#sendMetadata as - * grpc~ServerWritableStream#sendMetadata - * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerWritableStream#getPeer - * @param {grpc.internal~Call} call The call object to send data with - * @param {grpc.Metadata} metadata The request metadata from the client - * @param {grpc~serialize} serialize Serialization function for writes - */ -function ServerWritableStream(call, metadata, serialize) { - Writable.call(this, {objectMode: true}); - this.call = call; - - this.finished = false; - setUpWritable(this, serialize); - /** - * Indicates if the call has been cancelled - * @member {boolean} grpc~ServerWritableStream#cancelled - */ - this.cancelled = false; - /** - * The request metadata from the client - * @member {grpc.Metadata} grpc~ServerWritableStream#metadata - */ - this.metadata = metadata; - /** - * The request message from the client - * @member {*} grpc~ServerWritableStream#request - */ - this.request = undefined; -} - -/** - * Start writing a chunk of data. This is an implementation of a method required - * for implementing stream.Writable. - * @private - * @param {Buffer} chunk The chunk of data to write - * @param {string} encoding Used to pass write flags - * @param {function(Error=)} callback Callback to indicate that the write is - * complete - */ -function _write(chunk, encoding, callback) { - /* jshint validthis: true */ - var batch = {}; - var self = this; - var message; - try { - message = this.serialize(chunk); - } catch (e) { - e.code = constants.status.INTERNAL; - callback(e); - return; - } - if (!this.call.metadataSent) { - batch[grpc.opType.SEND_INITIAL_METADATA] = - (new Metadata())._getCoreRepresentation(); - this.call.metadataSent = true; - } - if (_.isFinite(encoding)) { - /* Attach the encoding if it is a finite number. This is the closest we - * can get to checking that it is valid flags */ - message.grpcWriteFlags = encoding; - } - batch[grpc.opType.SEND_MESSAGE] = message; - this.call.startBatch(batch, function(err, value) { - if (err) { - self.emit('error', err); - return; - } - callback(); - }); -} - -ServerWritableStream.prototype._write = _write; - -/** - * Emitted when the call has been cancelled. After this has been emitted, the - * call's `cancelled` property will be set to `true`. - * @event grpc~ServerReadableStream~cancelled - */ - -util.inherits(ServerReadableStream, Readable); - -/** - * A stream that the server can read from. Used for calls that are streaming - * from the client side. - * @constructor grpc~ServerReadableStream - * @extends external:Readable - * @borrows grpc~ServerUnaryCall#sendMetadata as - * grpc~ServerReadableStream#sendMetadata - * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerReadableStream#getPeer - * @param {grpc.internal~Call} call The call object to read data with - * @param {grpc.Metadata} metadata The request metadata from the client - * @param {grpc~deserialize} deserialize Deserialization function for reads - */ -function ServerReadableStream(call, metadata, deserialize) { - Readable.call(this, {objectMode: true}); - this.call = call; - setUpReadable(this, deserialize); - /** - * Indicates if the call has been cancelled - * @member {boolean} grpc~ServerReadableStream#cancelled - */ - this.cancelled = false; - /** - * The request metadata from the client - * @member {grpc.Metadata} grpc~ServerReadableStream#metadata - */ - this.metadata = metadata; -} - -/** - * Start reading from the gRPC data source. This is an implementation of a - * method required for implementing stream.Readable - * @access private - * @param {number} size Ignored - */ -function _read(size) { - /* jshint validthis: true */ - var self = this; - /** - * Callback to be called when a READ event is received. Pushes the data onto - * the read queue and starts reading again if applicable - * @param {grpc.Event} event READ event object - */ - function readCallback(err, event) { - if (err) { - self.terminate(); - return; - } - if (self.finished) { - self.push(null); - return; - } - var data = event.read; - var deserialized; - try { - deserialized = self.deserialize(data); - } catch (e) { - e.code = constants.status.INTERNAL; - self.emit('error', e); - return; - } - if (self.push(deserialized) && data !== null) { - var read_batch = {}; - read_batch[grpc.opType.RECV_MESSAGE] = true; - self.call.startBatch(read_batch, readCallback); - } else { - self.reading = false; - } - } - if (self.finished) { - self.push(null); - } else { - if (!self.reading) { - self.reading = true; - var batch = {}; - batch[grpc.opType.RECV_MESSAGE] = true; - self.call.startBatch(batch, readCallback); - } - } -} - -ServerReadableStream.prototype._read = _read; - -/** - * Emitted when the call has been cancelled. After this has been emitted, the - * call's `cancelled` property will be set to `true`. - * @event grpc~ServerDuplexStream~cancelled - */ - -util.inherits(ServerDuplexStream, Duplex); - -/** - * A stream that the server can read from or write to. Used for calls with - * duplex streaming. - * @constructor grpc~ServerDuplexStream - * @extends external:Duplex - * @borrows grpc~ServerUnaryCall#sendMetadata as - * grpc~ServerDuplexStream#sendMetadata - * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerDuplexStream#getPeer - * @param {grpc.internal~Call} call Call object to proxy - * @param {grpc.Metadata} metadata The request metadata from the client - * @param {grpc~serialize} serialize Serialization function for requests - * @param {grpc~deserialize} deserialize Deserialization function for - * responses - */ -function ServerDuplexStream(call, metadata, serialize, deserialize) { - Duplex.call(this, {objectMode: true}); - this.call = call; - setUpWritable(this, serialize); - setUpReadable(this, deserialize); - /** - * Indicates if the call has been cancelled - * @member {boolean} grpc~ServerReadableStream#cancelled - */ - this.cancelled = false; - /** - * The request metadata from the client - * @member {grpc.Metadata} grpc~ServerReadableStream#metadata - */ - this.metadata = metadata; -} - -ServerDuplexStream.prototype._read = _read; -ServerDuplexStream.prototype._write = _write; - -/** - * Send the initial metadata for a writable stream. - * @alias grpc~ServerUnaryCall#sendMetadata - * @param {grpc.Metadata} responseMetadata Metadata to send - */ -function sendMetadata(responseMetadata) { - /* jshint validthis: true */ - var self = this; - if (!this.call.metadataSent) { - this.call.metadataSent = true; - var batch = {}; - batch[grpc.opType.SEND_INITIAL_METADATA] = - responseMetadata._getCoreRepresentation(); - this.call.startBatch(batch, function(err) { - if (err) { - self.emit('error', err); - return; - } - }); - } -} - -ServerUnaryCall.prototype.sendMetadata = sendMetadata; -ServerWritableStream.prototype.sendMetadata = sendMetadata; -ServerReadableStream.prototype.sendMetadata = sendMetadata; -ServerDuplexStream.prototype.sendMetadata = sendMetadata; - -/** - * Get the endpoint this call/stream is connected to. - * @alias grpc~ServerUnaryCall#getPeer - * @return {string} The URI of the endpoint - */ -function getPeer() { - /* jshint validthis: true */ - return this.call.getPeer(); -} - -ServerUnaryCall.prototype.getPeer = getPeer; -ServerReadableStream.prototype.getPeer = getPeer; -ServerWritableStream.prototype.getPeer = getPeer; -ServerDuplexStream.prototype.getPeer = getPeer; - -/** - * Wait for the client to close, then emit a cancelled event if the client - * cancelled. - * @private - */ -function waitForCancel() { - /* jshint validthis: true */ - var self = this; - var cancel_batch = {}; - cancel_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true; - self.call.startBatch(cancel_batch, function(err, result) { - if (err) { - self.emit('error', err); - } - if (result.cancelled) { - self.cancelled = true; - self.emit('cancelled'); - } - }); -} - -ServerUnaryCall.prototype.waitForCancel = waitForCancel; -ServerReadableStream.prototype.waitForCancel = waitForCancel; -ServerWritableStream.prototype.waitForCancel = waitForCancel; -ServerDuplexStream.prototype.waitForCancel = waitForCancel; - -/** - * Callback function passed to server handlers that handle methods with unary - * responses. - * @callback grpc.Server~sendUnaryData - * @param {grpc~ServiceError} error An error, if the call failed - * @param {*} value The response value. Must be a valid argument to the - * `responseSerialize` method of the method that is being handled - * @param {grpc.Metadata=} trailer Trailing metadata to send, if applicable - * @param {grpc.writeFlags=} flags Flags to modify writing the response - */ - -/** - * User-provided method to handle unary requests on a server - * @callback grpc.Server~handleUnaryCall - * @param {grpc~ServerUnaryCall} call The call object - * @param {grpc.Server~sendUnaryData} callback The callback to call to respond - * to the request - */ - -/** - * Fully handle a unary call - * @private - * @param {grpc.internal~Call} call The call to handle - * @param {Object} handler Request handler object for the method that was called - * @param {grpc~Server.handleUnaryCall} handler.func The handler function - * @param {grpc~deserialize} handler.deserialize The deserialization function - * for request data - * @param {grpc~serialize} handler.serialize The serialization function for - * response data - * @param {grpc.Metadata} metadata Metadata from the client - */ -function handleUnary(call, handler, metadata) { - var emitter = new ServerUnaryCall(call, metadata); - emitter.on('error', function(error) { - handleError(call, error); - }); - emitter.waitForCancel(); - var batch = {}; - batch[grpc.opType.RECV_MESSAGE] = true; - call.startBatch(batch, function(err, result) { - if (err) { - handleError(call, err); - return; - } - try { - emitter.request = handler.deserialize(result.read); - } catch (e) { - e.code = constants.status.INTERNAL; - handleError(call, e); - return; - } - if (emitter.cancelled) { - return; - } - handler.func(emitter, function sendUnaryData(err, value, trailer, flags) { - if (err) { - if (trailer) { - err.metadata = trailer; - } - handleError(call, err); - } else { - sendUnaryResponse(call, value, handler.serialize, trailer, flags); - } - }); - }); -} - -/** - * User provided method to handle server streaming methods on the server. - * @callback grpc.Server~handleServerStreamingCall - * @param {grpc~ServerWritableStream} call The call object - */ - -/** - * Fully handle a server streaming call - * @private - * @param {grpc.internal~Call} call The call to handle - * @param {Object} handler Request handler object for the method that was called - * @param {grpc~Server.handleServerStreamingCall} handler.func The handler - * function - * @param {grpc~deserialize} handler.deserialize The deserialization function - * for request data - * @param {grpc~serialize} handler.serialize The serialization function for - * response data - * @param {grpc.Metadata} metadata Metadata from the client - */ -function handleServerStreaming(call, handler, metadata) { - var stream = new ServerWritableStream(call, metadata, handler.serialize); - stream.waitForCancel(); - var batch = {}; - batch[grpc.opType.RECV_MESSAGE] = true; - call.startBatch(batch, function(err, result) { - if (err) { - stream.emit('error', err); - return; - } - try { - stream.request = handler.deserialize(result.read); - } catch (e) { - e.code = constants.status.INTERNAL; - stream.emit('error', e); - return; - } - handler.func(stream); - }); -} - -/** - * User provided method to handle client streaming methods on the server. - * @callback grpc.Server~handleClientStreamingCall - * @param {grpc~ServerReadableStream} call The call object - * @param {grpc.Server~sendUnaryData} callback The callback to call to respond - * to the request - */ - -/** - * Fully handle a client streaming call - * @access private - * @param {grpc.internal~Call} call The call to handle - * @param {Object} handler Request handler object for the method that was called - * @param {grpc~Server.handleClientStreamingCall} handler.func The handler - * function - * @param {grpc~deserialize} handler.deserialize The deserialization function - * for request data - * @param {grpc~serialize} handler.serialize The serialization function for - * response data - * @param {grpc.Metadata} metadata Metadata from the client - */ -function handleClientStreaming(call, handler, metadata) { - var stream = new ServerReadableStream(call, metadata, handler.deserialize); - stream.on('error', function(error) { - handleError(call, error); - }); - stream.waitForCancel(); - handler.func(stream, function(err, value, trailer, flags) { - stream.terminate(); - if (err) { - if (trailer) { - err.metadata = trailer; - } - handleError(call, err); - } else { - sendUnaryResponse(call, value, handler.serialize, trailer, flags); - } - }); -} - -/** - * User provided method to handle bidirectional streaming calls on the server. - * @callback grpc.Server~handleBidiStreamingCall - * @param {grpc~ServerDuplexStream} call The call object - */ - -/** - * Fully handle a bidirectional streaming call - * @private - * @param {grpc.internal~Call} call The call to handle - * @param {Object} handler Request handler object for the method that was called - * @param {grpc~Server.handleBidiStreamingCall} handler.func The handler - * function - * @param {grpc~deserialize} handler.deserialize The deserialization function - * for request data - * @param {grpc~serialize} handler.serialize The serialization function for - * response data - * @param {Metadata} metadata Metadata from the client - */ -function handleBidiStreaming(call, handler, metadata) { - var stream = new ServerDuplexStream(call, metadata, handler.serialize, - handler.deserialize); - stream.waitForCancel(); - handler.func(stream); -} - -var streamHandlers = { - unary: handleUnary, - server_stream: handleServerStreaming, - client_stream: handleClientStreaming, - bidi: handleBidiStreaming -}; - -/** - * Constructs a server object that stores request handlers and delegates - * incoming requests to those handlers - * @memberof grpc - * @constructor - * @param {Object=} options Options that should be passed to the internal server - * implementation - * @example - * var server = new grpc.Server(); - * server.addProtoService(protobuf_service_descriptor, service_implementation); - * server.bind('address:port', server_credential); - * server.start(); - */ -function Server(options) { - this.handlers = {}; - var server = new grpc.Server(options); - this._server = server; - this.started = false; -} - -/** - * Start the server and begin handling requests - */ -Server.prototype.start = function() { - if (this.started) { - throw new Error('Server is already running'); - } - var self = this; - this.started = true; - this._server.start(); - /** - * Handles the SERVER_RPC_NEW event. If there is a handler associated with - * the requested method, use that handler to respond to the request. Then - * wait for the next request - * @param {grpc.internal~Event} event The event to handle with tag - * SERVER_RPC_NEW - */ - function handleNewCall(err, event) { - if (err) { - return; - } - var details = event.new_call; - var call = details.call; - var method = details.method; - var metadata = Metadata._fromCoreRepresentation(details.metadata); - if (method === null) { - return; - } - self._server.requestCall(handleNewCall); - var handler; - if (self.handlers.hasOwnProperty(method)) { - handler = self.handlers[method]; - } else { - var batch = {}; - batch[grpc.opType.SEND_INITIAL_METADATA] = - (new Metadata())._getCoreRepresentation(); - batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - code: constants.status.UNIMPLEMENTED, - details: '', - metadata: {} - }; - batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true; - call.startBatch(batch, function() {}); - return; - } - streamHandlers[handler.type](call, handler, metadata); - } - this._server.requestCall(handleNewCall); -}; - -/** - * Unified type for application handlers for all types of calls - * @typedef {(grpc.Server~handleUnaryCall - * |grpc.Server~handleClientStreamingCall - * |grpc.Server~handleServerStreamingCall - * |grpc.Server~handleBidiStreamingCall)} grpc.Server~handleCall - */ - -/** - * Registers a handler to handle the named method. Fails if there already is - * a handler for the given method. Returns true on success - * @param {string} name The name of the method that the provided function should - * handle/respond to. - * @param {grpc.Server~handleCall} handler Function that takes a stream of - * request values and returns a stream of response values - * @param {grpc~serialize} serialize Serialization function for responses - * @param {grpc~deserialize} deserialize Deserialization function for requests - * @param {string} type The streaming type of method that this handles - * @return {boolean} True if the handler was set. False if a handler was already - * set for that name. - */ -Server.prototype.register = function(name, handler, serialize, deserialize, - type) { - if (this.handlers.hasOwnProperty(name)) { - return false; - } - this.handlers[name] = { - func: handler, - serialize: serialize, - deserialize: deserialize, - type: type - }; - return true; -}; - -/** - * Gracefully shuts down the server. The server will stop receiving new calls, - * and any pending calls will complete. The callback will be called when all - * pending calls have completed and the server is fully shut down. This method - * is idempotent with itself and forceShutdown. - * @param {function()} callback The shutdown complete callback - */ -Server.prototype.tryShutdown = function(callback) { - this._server.tryShutdown(callback); -}; - -/** - * Forcibly shuts down the server. The server will stop receiving new calls - * and cancel all pending calls. When it returns, the server has shut down. - * This method is idempotent with itself and tryShutdown, and it will trigger - * any outstanding tryShutdown callbacks. - */ -Server.prototype.forceShutdown = function() { - this._server.forceShutdown(); -}; - -var unimplementedStatusResponse = { - code: constants.status.UNIMPLEMENTED, - details: 'The server does not implement this method' -}; - -var defaultHandler = { - unary: function(call, callback) { - callback(unimplementedStatusResponse); - }, - client_stream: function(call, callback) { - callback(unimplementedStatusResponse); - }, - server_stream: function(call) { - call.emit('error', unimplementedStatusResponse); - }, - bidi: function(call) { - call.emit('error', unimplementedStatusResponse); - } -}; - -/** - * Add a service to the server, with a corresponding implementation. - * @param {grpc~ServiceDefinition} service The service descriptor - * @param {Object} implementation Map of method - * names to method implementation for the provided service. - */ -Server.prototype.addService = function(service, implementation) { - if (!_.isObject(service) || !_.isObject(implementation)) { - throw new Error('addService requires two objects as arguments'); - } - if (_.keys(service).length === 0) { - throw new Error('Cannot add an empty service to a server'); - } - if (this.started) { - throw new Error('Can\'t add a service to a started server.'); - } - var self = this; - _.forOwn(service, function(attrs, name) { - var method_type; - if (attrs.requestStream) { - if (attrs.responseStream) { - method_type = 'bidi'; - } else { - method_type = 'client_stream'; - } - } else { - if (attrs.responseStream) { - method_type = 'server_stream'; - } else { - method_type = 'unary'; - } - } - var impl; - if (implementation[name] === undefined) { - /* Handle the case where the method is passed with the name exactly as - written in the proto file, instead of using JavaScript function - naming style */ - if (implementation[attrs.originalName] === undefined) { - common.log(constants.logVerbosity.ERROR, 'Method handler ' + name + - ' for ' + attrs.path + ' expected but not provided'); - impl = defaultHandler[method_type]; - } else { - impl = _.bind(implementation[attrs.originalName], implementation); - } - } else { - impl = _.bind(implementation[name], implementation); - } - var serialize = attrs.responseSerialize; - var deserialize = attrs.requestDeserialize; - var register_success = self.register(attrs.path, impl, serialize, - deserialize, method_type); - if (!register_success) { - throw new Error('Method handler for ' + attrs.path + - ' already provided.'); - } - }); -}; - -/** - * Add a proto service to the server, with a corresponding implementation - * @deprecated Use {@link grpc.Server#addService} instead - * @param {Protobuf.Reflect.Service} service The proto service descriptor - * @param {Object} implementation Map of method - * names to method implementation for the provided service. - */ -Server.prototype.addProtoService = util.deprecate(function(service, - implementation) { - var options; - var protobuf_js_5_common = require('./protobuf_js_5_common'); - var protobuf_js_6_common = require('./protobuf_js_6_common'); - if (protobuf_js_5_common.isProbablyProtobufJs5(service)) { - options = _.defaults(service.grpc_options, common.defaultGrpcOptions); - this.addService( - protobuf_js_5_common.getProtobufServiceAttrs(service, options), - implementation); - } else if (protobuf_js_6_common.isProbablyProtobufJs6(service)) { - options = _.defaults(service.grpc_options, common.defaultGrpcOptions); - this.addService( - protobuf_js_6_common.getProtobufServiceAttrs(service, options), - implementation); - } else { - // We assume that this is a service attributes object - this.addService(service, implementation); - } -}, 'Server#addProtoService: Use Server#addService instead'); - -/** - * Binds the server to the given port, with SSL disabled if creds is an - * insecure credentials object - * @param {string} port The port that the server should bind on, in the format - * "address:port" - * @param {grpc.ServerCredentials} creds Server credential object to be used for - * SSL. Pass an insecure credentials object for an insecure port. - */ -Server.prototype.bind = function(port, creds) { - if (this.started) { - throw new Error('Can\'t bind an already running server to an address'); - } - return this._server.addHttp2Port(port, creds); -}; - -exports.Server = Server; diff --git a/packages/grpc-native-core/templates/binding.gyp.template b/packages/grpc-native-core/templates/binding.gyp.template deleted file mode 100644 index 6385cd4e8..000000000 --- a/packages/grpc-native-core/templates/binding.gyp.template +++ /dev/null @@ -1,375 +0,0 @@ -%YAML 1.2 ---- | - # GRPC Node gyp file - # This currently builds the Node extension and dependencies - # This file has been automatically generated from a template file. - # Please look at the templates directory instead. - # This file can be regenerated from the template by running - # tools/buildgen/generate_projects.sh - - # 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. - - # Some of this file is built with the help of - # https://n8.io/converting-a-c-library-to-gyp/ - { - 'variables': { - 'runtime%': 'node', - # Some Node installations use the system installation of OpenSSL, and on - # some systems, the system OpenSSL still does not have ALPN support. This - # will let users recompile gRPC to work without ALPN. - 'grpc_alpn%': 'true', - # Indicates that the library should be built with gcov. - 'grpc_gcov%': 'false', - # Indicates that the library should be built with compatibility for musl - # libc, so that it can run on Alpine Linux. This is only necessary if not - # building on Alpine Linux - 'grpc_alpine%': 'false' - }, - 'target_defaults': { - 'configurations': { - % for name, args in configs.iteritems(): - % if name in ['dbg', 'opt']: - '${{'dbg':'Debug', 'opt': 'Release'}[name]}': { - % for arg, prop in [('CPPFLAGS', 'cflags'), ('DEFINES', 'defines')]: - % if args.get(arg, None) is not None: - '${prop}': [ - % for item in args.get(arg).split(): - '${item}', - % endfor - ], - % endif - % endfor - }, - % endif - % endfor - }, - % for arg, prop in [('CPPFLAGS', 'cflags'), ('LDFLAGS', 'ldflags')]: - % if defaults['global'].get(arg, None) is not None: - '${prop}': [ - % for item in defaults['global'].get(arg).split(): - '${item}', - % endfor - ], - % endif - % endfor - 'cflags_c': [ - '-Werror', - '-std=c99' - ], - 'cflags_cc': [ - '-Werror', - '-std=c++11' - ], - 'include_dirs': [ - 'deps/grpc', - 'deps/grpc/include' - ], - 'defines': [ - 'GPR_BACKWARDS_COMPATIBILITY_MODE', - 'GRPC_ARES=0', - 'GRPC_UV' - ], - 'conditions': [ - ['grpc_gcov=="true"', { - % for arg, prop in [('CPPFLAGS', 'cflags'), ('DEFINES', 'defines'), ('LDFLAGS', 'ldflags')]: - % if configs['gcov'].get(arg, None) is not None: - '${prop}': [ - % for item in configs['gcov'].get(arg).split(): - '${item}', - % endfor - ], - % endif - % endfor - }], - ['grpc_alpine=="true"', { - 'defines': [ - 'GPR_MUSL_LIBC_COMPAT' - ] - }], - ['OS!="win" and runtime=="electron"', { - "defines": [ - 'OPENSSL_NO_THREADS' - ] - }], - # This is the condition for using boringssl - ['OS=="win" or runtime=="electron"', { - "include_dirs": [ - "deps/grpc/third_party/boringssl/include" - ], - "defines": [ - 'OPENSSL_NO_ASM' - ] - }, { - 'conditions': [ - ["target_arch=='ia32'", { - "include_dirs": [ "<(node_root_dir)/deps/openssl/config/piii" ] - }], - ["target_arch=='x64'", { - "include_dirs": [ "<(node_root_dir)/deps/openssl/config/k8" ] - }], - ["target_arch=='arm'", { - "include_dirs": [ "<(node_root_dir)/deps/openssl/config/arm" ] - }], - ['grpc_alpn=="true"', { - 'defines': [ - 'TSI_OPENSSL_ALPN_SUPPORT=1' - ], - }, { - 'defines': [ - 'TSI_OPENSSL_ALPN_SUPPORT=0' - ], - }] - ], - 'include_dirs': [ - '<(node_root_dir)/deps/openssl/openssl/include', - ] - }], - ['OS == "win"', { - "include_dirs": [ - "deps/grpc/third_party/zlib", - "deps/grpc/third_party/cares/cares" - ], - "defines": [ - '_WIN32_WINNT=0x0600', - 'WIN32_LEAN_AND_MEAN', - '_HAS_EXCEPTIONS=0', - 'UNICODE', - '_UNICODE', - 'NOMINMAX', - ], - "msvs_settings": { - 'VCCLCompilerTool': { - 'RuntimeLibrary': 1, # static debug - } - }, - "libraries": [ - "ws2_32" - ] - }, { # OS != "win" - 'include_dirs': [ - '<(node_root_dir)/deps/zlib', - '<(node_root_dir)/deps/cares/include' - ] - }], - ['OS == "mac"', { - 'xcode_settings': { - % if defaults['global'].get('CPPFLAGS', None) is not None: - 'OTHER_CFLAGS': [ - % for item in defaults['global'].get('CPPFLAGS').split(): - '${item}', - % endfor - ], - 'OTHER_CPLUSPLUSFLAGS': [ - % for item in defaults['global'].get('CPPFLAGS').split(): - '${item}', - % endfor - '-stdlib=libc++', - '-std=c++11', - '-Wno-error=deprecated-declarations' - ], - % endif - }, - }] - ] - }, - 'conditions': [ - ['OS=="win" or runtime=="electron"', { - 'targets': [ - % for module in node_modules: - % for lib in libs: - % if lib.name in module.transitive_deps and lib.name == 'boringssl': - { - 'target_name': '${lib.name}', - 'product_prefix': 'lib', - 'type': 'static_library', - 'cflags': [ - '-Wno-implicit-fallthrough' - ], - 'dependencies': [ - % for dep in getattr(lib, 'deps', []): - '${dep}', - % endfor - ], - 'sources': [ - % for source in lib.src: - 'deps/grpc/${source}', - % endfor - ], - 'conditions': [ - ['OS == "mac"', { - 'xcode_settings': { - 'MACOSX_DEPLOYMENT_TARGET': '10.9' - } - }] - ] - }, - % endif - % endfor - % endfor - ], - }], - ['OS == "win" and runtime!="electron"', { - 'targets': [ - { - # IMPORTANT WINDOWS BUILD INFORMATION - # This library does not build on Windows without modifying the Node - # development packages that node-gyp downloads in order to build. - # Due to https://github.com/nodejs/node/issues/4932, the headers for - # BoringSSL conflict with the OpenSSL headers included by default - # when including the Node headers. The remedy for this is to remove - # the OpenSSL headers, from the downloaded Node development package, - # which is typically located in `.node-gyp` in your home directory. - # - # This is not true of Electron, which does not have OpenSSL headers. - 'target_name': 'WINDOWS_BUILD_WARNING', - 'rules': [ - { - 'rule_name': 'WINDOWS_BUILD_WARNING', - 'extension': 'S', - 'inputs': [ - 'package.json' - ], - 'outputs': [ - 'ignore_this_part' - ], - 'action': ['echo', 'IMPORTANT: Due to https://github.com/nodejs/node/issues/4932, to build this library on Windows, you must first remove <(node_root_dir)/include/node/openssl/'] - } - ] - }, - ] - }], - ['OS == "win"', { - 'targets': [ - # Only want to compile zlib under Windows - % for module in node_modules: - % for lib in libs: - % if lib.name in module.transitive_deps and lib.name == 'z': - { - 'target_name': '${lib.name}', - 'product_prefix': 'lib', - 'type': 'static_library', - 'dependencies': [ - % for dep in getattr(lib, 'deps', []): - '${dep}', - % endfor - ], - 'sources': [ - % for source in lib.src: - 'deps/grpc/${source}', - % endfor - ] - }, - % endif - % endfor - % endfor - ] - }] - ], - 'targets': [ - % for module in node_modules: - % for lib in libs: - % if lib.name in module.transitive_deps and lib.name not in ('boringssl', 'z'): - { - 'target_name': '${lib.name}', - 'product_prefix': 'lib', - 'type': 'static_library', - 'dependencies': [ - % for dep in getattr(lib, 'deps', []): - '${dep}', - % endfor - ], - 'sources': [ - % for source in lib.src: - 'deps/grpc/${source}', - % endfor - ], - 'conditions': [ - ['OS == "mac"', { - 'xcode_settings': { - 'MACOSX_DEPLOYMENT_TARGET': '10.9' - } - }] - ] - }, - % endif - % endfor - { - 'include_dirs': [ - "=4" - }, - "binary": { - "module_name": "grpc_node", - "module_path": "src/node/extension_binary/{node_abi}-{platform}-{arch}", - "host": "https://storage.googleapis.com/", - "remote_path": "grpc-precompiled-binaries/node/{name}/v{version}", - "package_name": "{node_abi}-{platform}-{arch}.tar.gz" - }, - "files": [ - "LICENSE", - "README.md", - "deps/grpc/etc/", - "index.js", - "index.d.ts", - "src/*.js", - "ext/*.{cc,h}", - "deps/grpc/include/grpc/**/*.h", - "deps/grpc/src/core/**/*.{c,h}", - "deps/grpc/src/boringssl/*.{c,h}", - "deps/grpc/third_party/nanopb/*.{c,h}", - "deps/grpc/third_party/zlib/**/*.{c,h}", - "deps/grpc/third_party/boringssl/crypto/**/*.{c,h}", - "deps/grpc/third_party/boringssl/include/**/*.{c,h}", - "deps/grpc/third_party/boringssl/ssl/**/*.{c,h}", - "binding.gyp" - ], - "main": "index.js", - "typings": "index.d.ts", - "license": "Apache-2.0", - "jshintConfig": { - "bitwise": true, - "curly": true, - "eqeqeq": true, - "esnext": true, - "freeze": true, - "immed": true, - "indent": 2, - "latedef": "nofunc", - "maxlen": 80, - "mocha": true, - "newcap": true, - "node": true, - "noarg": true, - "quotmark": "single", - "strict": true, - "trailing": true, - "undef": true, - "unused": "vars" - } - } diff --git a/packages/grpc-native-core/test/call_test.js b/packages/grpc-native-core/test/call_test.js deleted file mode 100644 index b5246c4f3..000000000 --- a/packages/grpc-native-core/test/call_test.js +++ /dev/null @@ -1,339 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); -var grpc = require('../src/grpc_extension'); -var constants = require('../src/constants'); - -/** - * Helper function to return an absolute deadline given a relative timeout in - * seconds. - * @param {number} timeout_secs The number of seconds to wait before timing out - * @return {Date} A date timeout_secs in the future - */ -function getDeadline(timeout_secs) { - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + timeout_secs); - return deadline; -} - -var insecureCreds = grpc.ChannelCredentials.createInsecure(); - -describe('call', function() { - var channel; - var server; - before(function() { - server = new grpc.Server(); - var port = server.addHttp2Port('localhost:0', - grpc.ServerCredentials.createInsecure()); - server.start(); - channel = new grpc.Channel('localhost:' + port, insecureCreds); - }); - after(function() { - server.forceShutdown(); - }); - describe('constructor', function() { - it('should reject anything less than 3 arguments', function() { - assert.throws(function() { - new grpc.Call(); - }, TypeError); - assert.throws(function() { - new grpc.Call(channel); - }, TypeError); - assert.throws(function() { - new grpc.Call(channel, 'method'); - }, TypeError); - }); - it('should succeed with a Channel, a string, and a date or number', - function() { - assert.doesNotThrow(function() { - new grpc.Call(channel, 'method', new Date()); - }); - assert.doesNotThrow(function() { - new grpc.Call(channel, 'method', 0); - }); - }); - it('should accept an optional fourth string parameter', function() { - assert.doesNotThrow(function() { - new grpc.Call(channel, 'method', new Date(), 'host_override'); - }); - }); - it('should fail with a closed channel', function() { - var local_channel = new grpc.Channel('hostname', insecureCreds); - local_channel.close(); - assert.throws(function() { - new grpc.Call(channel, 'method'); - }); - }); - it('should fail with other types', function() { - assert.throws(function() { - new grpc.Call({}, 'method', 0); - }, TypeError); - assert.throws(function() { - new grpc.Call(channel, null, 0); - }, TypeError); - assert.throws(function() { - new grpc.Call(channel, 'method', 'now'); - }, TypeError); - }); - it('should succeed without the new keyword', function() { - assert.doesNotThrow(function() { - var call = grpc.Call(channel, 'method', new Date()); - assert(call instanceof grpc.Call); - }); - }); - }); - describe('deadline', function() { - it('should time out immediately with negative deadline', function(done) { - var call = new grpc.Call(channel, 'method', -Infinity); - var batch = {}; - batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(batch, function(err, response) { - assert.strictEqual(response.status.code, - constants.status.DEADLINE_EXCEEDED); - done(); - }); - }); - }); - describe('startBatch', function() { - it('should fail without an object and a function', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - call.startBatch(); - }); - assert.throws(function() { - call.startBatch({}); - }); - assert.throws(function() { - call.startBatch(null, function(){}); - }); - }); - it('should succeed with an empty object', function(done) { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.doesNotThrow(function() { - call.startBatch({}, function(err) { - assert.ifError(err); - done(); - }); - }); - }); - }); - describe('startBatch with metadata', function() { - it('should succeed with a map of strings to string arrays', function(done) { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.doesNotThrow(function() { - var batch = {}; - batch[grpc.opType.SEND_INITIAL_METADATA] = {'key1': ['value1'], - 'key2': ['value2']}; - call.startBatch(batch, function(err, resp) { - assert.ifError(err); - assert.deepEqual(resp, {'send_metadata': true}); - done(); - }); - }); - }); - it('should succeed with a map of strings to buffer arrays', function(done) { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.doesNotThrow(function() { - var batch = {}; - batch[grpc.opType.SEND_INITIAL_METADATA] = { - 'key1-bin': [new Buffer('value1')], - 'key2-bin': [new Buffer('value2')] - }; - call.startBatch(batch, function(err, resp) { - assert.ifError(err); - assert.deepEqual(resp, {'send_metadata': true}); - done(); - }); - }); - }); - it('should fail with other parameter types', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_INITIAL_METADATA] = undefined; - call.startBatch(batch, function(){}); - }); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_INITIAL_METADATA] = null; - call.startBatch(batch, function(){}); - }, TypeError); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_INITIAL_METADATA] = 'value'; - call.startBatch(batch, function(){}); - }, TypeError); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_INITIAL_METADATA] = 5; - call.startBatch(batch, function(){}); - }, TypeError); - }); - }); - describe('startBatch with message', function() { - it('should fail with null argument', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_MESSAGE] = null; - call.startBatch(batch, function(){}); - }, TypeError); - }); - it('should fail with numeric argument', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_MESSAGE] = 5; - call.startBatch(batch, function(){}); - }, TypeError); - }); - it('should fail with string argument', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_MESSAGE] = 'value'; - call.startBatch(batch, function(){}); - }, TypeError); - }); - }); - describe('startBatch with status', function() { - it('should fail without a code', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - details: 'details string', - metadata: {} - }; - call.startBatch(batch, function(){}); - }, TypeError); - }); - it('should fail without details', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - code: 0, - metadata: {} - }; - call.startBatch(batch, function(){}); - }, TypeError); - }); - it('should fail without metadata', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - code: 0, - details: 'details string' - }; - call.startBatch(batch, function(){}); - }, TypeError); - }); - it('should fail with incorrectly typed code argument', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - code: 'code string', - details: 'details string', - metadata: {} - }; - call.startBatch(batch, function(){}); - }, TypeError); - }); - it('should fail with incorrectly typed details argument', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - code: 0, - details: 5, - metadata: {} - }; - call.startBatch(batch, function(){}); - }, TypeError); - }); - it('should fail with incorrectly typed metadata argument', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.throws(function() { - var batch = {}; - batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - code: 0, - details: 'details string', - metadata: 'abc' - }; - call.startBatch(batch, function(){}); - }, TypeError); - }); - }); - describe('cancel', function() { - it('should succeed', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.doesNotThrow(function() { - call.cancel(); - }); - }); - }); - describe('cancelWithStatus', function() { - it('should reject anything other than an integer and a string', function() { - assert.doesNotThrow(function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - call.cancelWithStatus(1, 'details'); - }); - assert.throws(function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - call.cancelWithStatus(); - }); - assert.throws(function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - call.cancelWithStatus(''); - }); - assert.throws(function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - call.cancelWithStatus(5, {}); - }); - }); - it('should reject the OK status code', function() { - assert.throws(function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - call.cancelWithStatus(0, 'details'); - }); - }); - it('should result in the call ending with a status', function(done) { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - var batch = {}; - batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(batch, function(err, response) { - assert.strictEqual(response.status.code, 5); - assert.strictEqual(response.status.details, 'details'); - done(); - }); - call.cancelWithStatus(5, 'details'); - }); - }); - describe('getPeer', function() { - it('should return a string', function() { - var call = new grpc.Call(channel, 'method', getDeadline(1)); - assert.strictEqual(typeof call.getPeer(), 'string'); - }); - }); -}); diff --git a/packages/grpc-native-core/test/channel_test.js b/packages/grpc-native-core/test/channel_test.js deleted file mode 100644 index 373c5ac3c..000000000 --- a/packages/grpc-native-core/test/channel_test.js +++ /dev/null @@ -1,181 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); -var grpc = require('../src/grpc_extension'); - -/** - * This is used for testing functions with multiple asynchronous calls that - * can happen in different orders. This should be passed the number of async - * function invocations that can occur last, and each of those should call this - * function's return value - * @param {function()} done The function that should be called when a test is - * complete. - * @param {number} count The number of calls to the resulting function if the - * test passes. - * @return {function()} The function that should be called at the end of each - * sequence of asynchronous functions. - */ -function multiDone(done, count) { - return function() { - count -= 1; - if (count <= 0) { - done(); - } - }; -} -var insecureCreds = grpc.ChannelCredentials.createInsecure(); - -describe('channel', function() { - describe('constructor', function() { - it('should require a string for the first argument', function() { - assert.doesNotThrow(function() { - new grpc.Channel('hostname', insecureCreds); - }); - assert.throws(function() { - new grpc.Channel(); - }, TypeError); - assert.throws(function() { - new grpc.Channel(5); - }); - }); - it('should require a credential for the second argument', function() { - assert.doesNotThrow(function() { - new grpc.Channel('hostname', insecureCreds); - }); - assert.throws(function() { - new grpc.Channel('hostname', 5); - }); - assert.throws(function() { - new grpc.Channel('hostname'); - }); - }); - it('should accept an object for the third argument', function() { - assert.doesNotThrow(function() { - new grpc.Channel('hostname', insecureCreds, {}); - }); - assert.throws(function() { - new grpc.Channel('hostname', insecureCreds, 'abc'); - }); - }); - it('should only accept objects with string or int values', function() { - assert.doesNotThrow(function() { - new grpc.Channel('hostname', insecureCreds,{'key' : 'value'}); - }); - assert.doesNotThrow(function() { - new grpc.Channel('hostname', insecureCreds, {'key' : 5}); - }); - assert.throws(function() { - new grpc.Channel('hostname', insecureCreds, {'key' : null}); - }); - assert.throws(function() { - new grpc.Channel('hostname', insecureCreds, {'key' : new Date()}); - }); - }); - it('should succeed without the new keyword', function() { - assert.doesNotThrow(function() { - var channel = grpc.Channel('hostname', insecureCreds); - assert(channel instanceof grpc.Channel); - }); - }); - }); - describe('close', function() { - var channel; - beforeEach(function() { - channel = new grpc.Channel('hostname', insecureCreds, {}); - }); - it('should succeed silently', function() { - assert.doesNotThrow(function() { - channel.close(); - }); - }); - it('should be idempotent', function() { - assert.doesNotThrow(function() { - channel.close(); - channel.close(); - }); - }); - }); - describe('getTarget', function() { - var channel; - beforeEach(function() { - channel = new grpc.Channel('hostname', insecureCreds, {}); - }); - it('should return a string', function() { - assert.strictEqual(typeof channel.getTarget(), 'string'); - }); - }); - describe('getConnectivityState', function() { - var channel; - beforeEach(function() { - channel = new grpc.Channel('hostname', insecureCreds, {}); - }); - it('should return IDLE for a new channel', function() { - assert.strictEqual(channel.getConnectivityState(), - grpc.connectivityState.IDLE); - }); - }); - describe('watchConnectivityState', function() { - var channel; - beforeEach(function() { - channel = new grpc.Channel('localhost', insecureCreds, {}); - }); - afterEach(function() { - channel.close(); - }); - it('should time out if called alone', function(done) { - var old_state = channel.getConnectivityState(); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - channel.watchConnectivityState(old_state, deadline, function(err, value) { - assert(err); - done(); - }); - }); - it('should complete if a connection attempt is forced', function(done) { - var old_state = channel.getConnectivityState(); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - channel.watchConnectivityState(old_state, deadline, function(err, value) { - assert.ifError(err); - assert.notEqual(value.new_state, old_state); - done(); - }); - channel.getConnectivityState(true); - }); - it('should complete twice if called twice', function(done) { - done = multiDone(done, 2); - var old_state = channel.getConnectivityState(); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - channel.watchConnectivityState(old_state, deadline, function(err, value) { - assert.ifError(err); - assert.notEqual(value.new_state, old_state); - done(); - }); - channel.watchConnectivityState(old_state, deadline, function(err, value) { - assert.ifError(err); - assert.notEqual(value.new_state, old_state); - done(); - }); - channel.getConnectivityState(true); - }); - }); -}); diff --git a/packages/grpc-native-core/test/common_test.js b/packages/grpc-native-core/test/common_test.js deleted file mode 100644 index d50c1a276..000000000 --- a/packages/grpc-native-core/test/common_test.js +++ /dev/null @@ -1,190 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); -var _ = require('lodash'); - -var common = require('../src/common'); -var protobuf_js_5_common = require('../src/protobuf_js_5_common'); - -var serializeCls = protobuf_js_5_common.serializeCls; -var deserializeCls = protobuf_js_5_common.deserializeCls; - -var ProtoBuf = require('protobufjs'); - -var messages_proto = ProtoBuf.loadProtoFile( - __dirname + '/test_messages.proto').build(); - -var default_options = common.defaultGrpcOptions; - -describe('Proto message long int serialize and deserialize', function() { - var longSerialize = serializeCls(messages_proto.LongValues); - var longDeserialize = deserializeCls(messages_proto.LongValues, - default_options); - var pos_value = '314159265358979'; - var neg_value = '-27182818284590'; - it('should preserve positive int64 values', function() { - var serialized = longSerialize({int_64: pos_value}); - assert.strictEqual(longDeserialize(serialized).int_64.toString(), - pos_value); - }); - it('should preserve negative int64 values', function() { - var serialized = longSerialize({int_64: neg_value}); - assert.strictEqual(longDeserialize(serialized).int_64.toString(), - neg_value); - }); - it('should preserve uint64 values', function() { - var serialized = longSerialize({uint_64: pos_value}); - assert.strictEqual(longDeserialize(serialized).uint_64.toString(), - pos_value); - }); - it('should preserve positive sint64 values', function() { - var serialized = longSerialize({sint_64: pos_value}); - assert.strictEqual(longDeserialize(serialized).sint_64.toString(), - pos_value); - }); - it('should preserve negative sint64 values', function() { - var serialized = longSerialize({sint_64: neg_value}); - assert.strictEqual(longDeserialize(serialized).sint_64.toString(), - neg_value); - }); - it('should preserve fixed64 values', function() { - var serialized = longSerialize({fixed_64: pos_value}); - assert.strictEqual(longDeserialize(serialized).fixed_64.toString(), - pos_value); - }); - it('should preserve positive sfixed64 values', function() { - var serialized = longSerialize({sfixed_64: pos_value}); - assert.strictEqual(longDeserialize(serialized).sfixed_64.toString(), - pos_value); - }); - it('should preserve negative sfixed64 values', function() { - var serialized = longSerialize({sfixed_64: neg_value}); - assert.strictEqual(longDeserialize(serialized).sfixed_64.toString(), - neg_value); - }); - it('should deserialize as a number with the right option set', function() { - var num_options = _.defaults({longsAsStrings: false}, default_options); - var longNumDeserialize = deserializeCls(messages_proto.LongValues, - num_options); - var serialized = longSerialize({int_64: pos_value}); - assert.strictEqual(typeof longDeserialize(serialized).int_64, 'string'); - /* With the longsAsStrings option disabled, long values are represented as - * objects with 3 keys: low, high, and unsigned */ - assert.strictEqual(typeof longNumDeserialize(serialized).int_64, 'object'); - }); -}); -describe('Proto message bytes serialize and deserialize', function() { - var sequenceSerialize = serializeCls(messages_proto.SequenceValues); - var sequenceDeserialize = deserializeCls( - messages_proto.SequenceValues, default_options); - var b64_options = _.defaults({binaryAsBase64: true}, default_options); - var sequenceBase64Deserialize = deserializeCls( - messages_proto.SequenceValues, b64_options); - var buffer_val = new Buffer([0x69, 0xb7]); - var base64_val = 'abc='; - it('should preserve a buffer', function() { - var serialized = sequenceSerialize({bytes_field: buffer_val}); - var deserialized = sequenceDeserialize(serialized); - assert.strictEqual(deserialized.bytes_field.compare(buffer_val), 0); - }); - it('should accept base64 encoded strings', function() { - var serialized = sequenceSerialize({bytes_field: base64_val}); - var deserialized = sequenceDeserialize(serialized); - assert.strictEqual(deserialized.bytes_field.compare(buffer_val), 0); - }); - it('should output base64 encoded strings with an option set', function() { - var serialized = sequenceSerialize({bytes_field: base64_val}); - var deserialized = sequenceBase64Deserialize(serialized); - assert.strictEqual(deserialized.bytes_field, base64_val); - }); - it('should serialize a repeated field as packed by default', function() { - var expected_serialize = new Buffer([0x12, 0x01, 0x0a]); - var serialized = sequenceSerialize({repeated_field: [10]}); - assert.strictEqual(expected_serialize.compare(serialized), 0); - }); - // This tests a bug that was fixed in Protobuf.js 6 - it.skip('should deserialize packed or unpacked repeated', function() { - var expectedDeserialize = { - bytes_field: new Buffer(''), - repeated_field: [10] - }; - var packedSerialized = new Buffer([0x12, 0x01, 0x0a]); - var unpackedSerialized = new Buffer([0x10, 0x0a]); - var packedDeserialized; - var unpackedDeserialized; - assert.doesNotThrow(function() { - packedDeserialized = sequenceDeserialize(packedSerialized); - }); - assert.doesNotThrow(function() { - unpackedDeserialized = sequenceDeserialize(unpackedSerialized); - }); - assert.deepEqual(packedDeserialized, expectedDeserialize); - assert.deepEqual(unpackedDeserialized, expectedDeserialize); - }); -}); -// This tests a bug that was fixed in Protobuf.js 6 -describe.skip('Proto message oneof serialize and deserialize', function() { - var oneofSerialize = serializeCls(messages_proto.OneOfValues); - var oneofDeserialize = deserializeCls( - messages_proto.OneOfValues, default_options); - it('Should have idempotent round trips', function() { - var test_message = {oneof_choice: 'int_choice', int_choice: 5}; - var serialized1 = oneofSerialize(test_message); - var deserialized1 = oneofDeserialize(serialized1); - assert.equal(deserialized1.int_choice, 5); - var serialized2 = oneofSerialize(deserialized1); - var deserialized2 = oneofDeserialize(serialized2); - assert.deepEqual(deserialized1, deserialized2); - }); - it('Should emit a property indicating which field was chosen', function() { - var test_message1 = {oneof_choice: 'int_choice', int_choice: 5}; - var serialized1 = oneofSerialize(test_message1); - var deserialized1 = oneofDeserialize(serialized1); - assert.equal(deserialized1.oneof_choice, 'int_choice'); - var test_message2 = {oneof_choice: 'string_choice', string_choice: 'abc'}; - var serialized2 = oneofSerialize(test_message2); - var deserialized2 = oneofDeserialize(serialized2); - assert.equal(deserialized2.oneof_choice, 'string_choice'); - }); -}); -describe('Proto message enum serialize and deserialize', function() { - var enumSerialize = serializeCls(messages_proto.EnumValues); - var enumDeserialize = deserializeCls( - messages_proto.EnumValues, default_options); - var enumIntOptions = _.defaults({enumsAsStrings: false}, default_options); - var enumIntDeserialize = deserializeCls( - messages_proto.EnumValues, enumIntOptions); - it('Should accept both names and numbers', function() { - var nameSerialized = enumSerialize({enum_value: 'ONE'}); - var numberSerialized = enumSerialize({enum_value: 1}); - assert.strictEqual(messages_proto.TestEnum.ONE, 1); - assert.deepEqual(enumDeserialize(nameSerialized), - enumDeserialize(numberSerialized)); - }); - // This tests a bug that was fixed in Protobuf.js 6 - it.skip('Should correctly handle the enumsAsStrings option', function() { - var serialized = enumSerialize({enum_value: 'TWO'}); - var nameDeserialized = enumDeserialize(serialized); - var numberDeserialized = enumIntDeserialize(serialized); - assert.deepEqual(nameDeserialized, {enum_value: 'TWO'}); - assert.deepEqual(numberDeserialized, {enum_value: 2}); - }); -}); diff --git a/packages/grpc-native-core/test/end_to_end_test.js b/packages/grpc-native-core/test/end_to_end_test.js deleted file mode 100644 index c11dfa93c..000000000 --- a/packages/grpc-native-core/test/end_to_end_test.js +++ /dev/null @@ -1,292 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); -var grpc = require('../src/grpc_extension'); -var constants = require('../src/constants'); - -/** - * This is used for testing functions with multiple asynchronous calls that - * can happen in different orders. This should be passed the number of async - * function invocations that can occur last, and each of those should call this - * function's return value - * @param {function()} done The function that should be called when a test is - * complete. - * @param {number} count The number of calls to the resulting function if the - * test passes. - * @return {function()} The function that should be called at the end of each - * sequence of asynchronous functions. - */ -function multiDone(done, count) { - return function() { - count -= 1; - if (count <= 0) { - done(); - } - }; -} - -var insecureCreds = grpc.ChannelCredentials.createInsecure(); - -describe('end-to-end', function() { - var server; - var channel; - before(function() { - server = new grpc.Server(); - var port_num = server.addHttp2Port('0.0.0.0:0', - grpc.ServerCredentials.createInsecure()); - server.start(); - channel = new grpc.Channel('localhost:' + port_num, insecureCreds); - }); - after(function() { - server.forceShutdown(); - }); - it('should start and end a request without error', function(complete) { - var done = multiDone(complete, 2); - var status_text = 'xyz'; - var call = new grpc.Call(channel, - 'dummy_method', - Infinity); - var client_batch = {}; - client_batch[grpc.opType.SEND_INITIAL_METADATA] = {}; - client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - client_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(client_batch, function(err, response) { - assert.ifError(err); - assert.deepEqual(response, { - send_metadata: true, - client_close: true, - metadata: {}, - status: { - code: constants.status.OK, - details: status_text, - metadata: {} - } - }); - done(); - }); - - server.requestCall(function(err, call_details) { - var new_call = call_details.new_call; - assert.notEqual(new_call, null); - var server_call = new_call.call; - assert.notEqual(server_call, null); - var server_batch = {}; - server_batch[grpc.opType.SEND_INITIAL_METADATA] = {}; - server_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - metadata: {}, - code: constants.status.OK, - details: status_text - }; - server_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true; - server_call.startBatch(server_batch, function(err, response) { - assert.ifError(err); - assert.deepEqual(response, { - send_metadata: true, - send_status: true, - cancelled: false - }); - done(); - }); - }); - }); - it('should successfully send and receive metadata', function(complete) { - var done = multiDone(complete, 2); - var status_text = 'xyz'; - var call = new grpc.Call(channel, - 'dummy_method', - Infinity); - var client_batch = {}; - client_batch[grpc.opType.SEND_INITIAL_METADATA] = { - client_key: ['client_value'] - }; - client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - client_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(client_batch, function(err, response) { - assert.ifError(err); - assert.deepEqual(response,{ - send_metadata: true, - client_close: true, - metadata: {server_key: ['server_value']}, - status: {code: constants.status.OK, - details: status_text, - metadata: {}} - }); - done(); - }); - - server.requestCall(function(err, call_details) { - var new_call = call_details.new_call; - assert.notEqual(new_call, null); - assert.strictEqual(new_call.metadata.client_key[0], - 'client_value'); - var server_call = new_call.call; - assert.notEqual(server_call, null); - var server_batch = {}; - server_batch[grpc.opType.SEND_INITIAL_METADATA] = { - server_key: ['server_value'] - }; - server_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - metadata: {}, - code: constants.status.OK, - details: status_text - }; - server_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true; - server_call.startBatch(server_batch, function(err, response) { - assert.ifError(err); - assert.deepEqual(response, { - send_metadata: true, - send_status: true, - cancelled: false - }); - done(); - }); - }); - }); - it('should send and receive data without error', function(complete) { - var req_text = 'client_request'; - var reply_text = 'server_response'; - var done = multiDone(complete, 2); - var status_text = 'success'; - var call = new grpc.Call(channel, - 'dummy_method', - Infinity); - var client_batch = {}; - client_batch[grpc.opType.SEND_INITIAL_METADATA] = {}; - client_batch[grpc.opType.SEND_MESSAGE] = new Buffer(req_text); - client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - client_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - client_batch[grpc.opType.RECV_MESSAGE] = true; - client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(client_batch, function(err, response) { - assert.ifError(err); - assert(response.send_metadata); - assert(response.client_close); - assert.deepEqual(response.metadata, {}); - assert(response.send_message); - assert.strictEqual(response.read.toString(), reply_text); - assert.deepEqual(response.status, {code: constants.status.OK, - details: status_text, - metadata: {}}); - done(); - }); - - server.requestCall(function(err, call_details) { - var new_call = call_details.new_call; - assert.notEqual(new_call, null); - var server_call = new_call.call; - assert.notEqual(server_call, null); - var server_batch = {}; - server_batch[grpc.opType.SEND_INITIAL_METADATA] = {}; - server_batch[grpc.opType.RECV_MESSAGE] = true; - server_call.startBatch(server_batch, function(err, response) { - assert.ifError(err); - assert(response.send_metadata); - assert.strictEqual(response.read.toString(), req_text); - var response_batch = {}; - response_batch[grpc.opType.SEND_MESSAGE] = new Buffer(reply_text); - response_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - metadata: {}, - code: constants.status.OK, - details: status_text - }; - response_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true; - server_call.startBatch(response_batch, function(err, response) { - assert(response.send_status); - assert(!response.cancelled); - done(); - }); - }); - }); - }); - it('should send multiple messages', function(complete) { - var done = multiDone(complete, 2); - var requests = ['req1', 'req2']; - var status_text = 'xyz'; - var call = new grpc.Call(channel, - 'dummy_method', - Infinity); - var client_batch = {}; - client_batch[grpc.opType.SEND_INITIAL_METADATA] = {}; - client_batch[grpc.opType.SEND_MESSAGE] = new Buffer(requests[0]); - client_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - call.startBatch(client_batch, function(err, response) { - assert.ifError(err); - assert.deepEqual(response, { - send_metadata: true, - send_message: true, - metadata: {} - }); - var req2_batch = {}; - req2_batch[grpc.opType.SEND_MESSAGE] = new Buffer(requests[1]); - req2_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - req2_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(req2_batch, function(err, resp) { - assert.ifError(err); - assert.deepEqual(resp, { - send_message: true, - client_close: true, - status: { - code: constants.status.OK, - details: status_text, - metadata: {} - } - }); - done(); - }); - }); - - server.requestCall(function(err, call_details) { - var new_call = call_details.new_call; - assert.notEqual(new_call, null); - var server_call = new_call.call; - assert.notEqual(server_call, null); - var server_batch = {}; - server_batch[grpc.opType.SEND_INITIAL_METADATA] = {}; - server_batch[grpc.opType.RECV_MESSAGE] = true; - server_call.startBatch(server_batch, function(err, response) { - assert.ifError(err); - assert(response.send_metadata); - assert.strictEqual(response.read.toString(), requests[0]); - var snd_batch = {}; - snd_batch[grpc.opType.RECV_MESSAGE] = true; - server_call.startBatch(snd_batch, function(err, response) { - assert.ifError(err); - assert.strictEqual(response.read.toString(), requests[1]); - var end_batch = {}; - end_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true; - end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { - metadata: {}, - code: constants.status.OK, - details: status_text - }; - server_call.startBatch(end_batch, function(err, response) { - assert.ifError(err); - assert(response.send_status); - assert(!response.cancelled); - done(); - }); - }); - }); - }); - }); -}); diff --git a/packages/grpc-native-core/test/server_test.js b/packages/grpc-native-core/test/server_test.js deleted file mode 100644 index c1b941948..000000000 --- a/packages/grpc-native-core/test/server_test.js +++ /dev/null @@ -1,138 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); -var fs = require('fs'); -var path = require('path'); -var grpc = require('../src/grpc_extension'); - -describe('server', function() { - describe('constructor', function() { - it('should work with no arguments', function() { - assert.doesNotThrow(function() { - new grpc.Server(); - }); - }); - it('should work with an empty object argument', function() { - assert.doesNotThrow(function() { - new grpc.Server({}); - }); - }); - it('should work without the new keyword', function() { - var server; - assert.doesNotThrow(function() { - server = grpc.Server(); - }); - assert(server instanceof grpc.Server); - }); - it('should only accept objects with string or int values', function() { - assert.doesNotThrow(function() { - new grpc.Server({'key' : 'value'}); - }); - assert.doesNotThrow(function() { - new grpc.Server({'key' : 5}); - }); - assert.throws(function() { - new grpc.Server({'key' : null}); - }); - assert.throws(function() { - new grpc.Server({'key' : new Date()}); - }); - }); - }); - describe('addHttp2Port', function() { - var server; - before(function() { - server = new grpc.Server(); - }); - it('should bind to an unused port', function() { - var port; - assert.doesNotThrow(function() { - port = server.addHttp2Port('0.0.0.0:0', - grpc.ServerCredentials.createInsecure()); - }); - assert(port > 0); - }); - it('should bind to an unused port with ssl credentials', function() { - var port; - var key_path = path.join(__dirname, '../../../test/data/server1.key'); - var pem_path = path.join(__dirname, '../../../test/data/server1.pem'); - var key_data = fs.readFileSync(key_path); - var pem_data = fs.readFileSync(pem_path); - var creds = grpc.ServerCredentials.createSsl(null, - [{private_key: key_data, - cert_chain: pem_data}]); - assert.doesNotThrow(function() { - port = server.addHttp2Port('0.0.0.0:0', creds); - }); - assert(port > 0); - }); - }); - describe('addSecureHttp2Port', function() { - var server; - before(function() { - server = new grpc.Server(); - }); - }); - describe('start', function() { - var server; - before(function() { - server = new grpc.Server(); - server.addHttp2Port('0.0.0.0:0', grpc.ServerCredentials.createInsecure()); - }); - after(function() { - server.forceShutdown(); - }); - it('should start without error', function() { - assert.doesNotThrow(function() { - server.start(); - }); - }); - }); - describe('shutdown', function() { - var server; - beforeEach(function() { - server = new grpc.Server(); - server.addHttp2Port('0.0.0.0:0', grpc.ServerCredentials.createInsecure()); - server.start(); - }); - afterEach(function() { - server.forceShutdown(); - }); - it('tryShutdown should shutdown successfully', function(done) { - server.tryShutdown(done); - }); - it('forceShutdown should shutdown successfully', function() { - server.forceShutdown(); - }); - it('tryShutdown should be idempotent', function(done) { - server.tryShutdown(done); - server.tryShutdown(function() {}); - }); - it('forceShutdown should be idempotent', function() { - server.forceShutdown(); - server.forceShutdown(); - }); - it('forceShutdown should trigger tryShutdown', function(done) { - server.tryShutdown(done); - server.forceShutdown(); - }); - }); -}); diff --git a/packages/grpc-native-core/tools/run_tests/artifacts/build_artifact_node.bat b/packages/grpc-native-core/tools/run_tests/artifacts/build_artifact_node.bat deleted file mode 100644 index 9563da7db..000000000 --- a/packages/grpc-native-core/tools/run_tests/artifacts/build_artifact_node.bat +++ /dev/null @@ -1,48 +0,0 @@ -@rem Copyright 2016 gRPC authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. - -set node_versions=4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 9.0.0 - -set electron_versions=1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 - -set PATH=%PATH%;C:\Program Files\nodejs\;%APPDATA%\npm - -del /f /q BUILD || rmdir build /s /q - -call npm update || goto :error - -mkdir -p %ARTIFACTS_OUT% - -for %%v in (%node_versions%) do ( - call .\node_modules\.bin\node-pre-gyp.cmd configure build --target=%%v --target_arch=%1 - -@rem Try again after removing openssl headers - rmdir "%USERPROFILE%\.node-gyp\%%v\include\node\openssl" /S /Q - rmdir "%USERPROFILE%\.node-gyp\iojs-%%v\include\node\openssl" /S /Q - call .\node_modules\.bin\node-pre-gyp.cmd build package --target=%%v --target_arch=%1 || goto :error - - xcopy /Y /I /S build\stage\* %ARTIFACTS_OUT%\ || goto :error -) - -for %%v in (%electron_versions%) do ( - cmd /V /C "set "HOME=%USERPROFILE%\electron-gyp" && call .\node_modules\.bin\node-pre-gyp.cmd configure rebuild package --runtime=electron --target=%%v --target_arch=%1 --disturl=https://atom.io/download/electron" || goto :error - - xcopy /Y /I /S build\stage\* %ARTIFACTS_OUT%\ || goto :error -) -if %errorlevel% neq 0 exit /b %errorlevel% - -goto :EOF - -:error -exit /b 1 diff --git a/packages/grpc-native-core/tools/run_tests/artifacts/build_artifact_node.sh b/packages/grpc-native-core/tools/run_tests/artifacts/build_artifact_node.sh deleted file mode 100755 index 3b4d0456f..000000000 --- a/packages/grpc-native-core/tools/run_tests/artifacts/build_artifact_node.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -# Copyright 2016 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. - -NODE_TARGET_ARCH=$1 -NODE_TARGET_OS=$2 -source ~/.nvm/nvm.sh - -nvm use 8 -set -ex - -umask 022 - -cd $(dirname $0)/../../.. - -rm -rf build || true - -mkdir -p "${ARTIFACTS_OUT}" - -npm update - -node_versions=( 4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 9.0.0 ) - -electron_versions=( 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 ) - -for version in ${node_versions[@]} -do - ./node_modules/.bin/node-pre-gyp configure rebuild package --target=$version --target_arch=$NODE_TARGET_ARCH --grpc_alpine=true - cp -r build/stage/* "${ARTIFACTS_OUT}"/ - if [ "$NODE_TARGET_ARCH" == 'x64' ] && [ "$NODE_TARGET_OS" == 'linux' ] - then - # Cross compile for ARM on x64 - CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ LD=arm-linux-gnueabihf-g++ ./node_modules/.bin/node-pre-gyp configure rebuild package testpackage --target=$version --target_arch=arm - cp -r build/stage/* "${ARTIFACTS_OUT}"/ - fi -done - -for version in ${electron_versions[@]} -do - HOME=~/.electron-gyp ./node_modules/.bin/node-pre-gyp configure rebuild package --runtime=electron --target=$version --target_arch=$NODE_TARGET_ARCH --disturl=https://atom.io/download/electron - cp -r build/stage/* "${ARTIFACTS_OUT}"/ -done diff --git a/packages/grpc-native-core/tools/run_tests/artifacts/build_package_node.sh b/packages/grpc-native-core/tools/run_tests/artifacts/build_package_node.sh deleted file mode 100755 index 2860f68bc..000000000 --- a/packages/grpc-native-core/tools/run_tests/artifacts/build_package_node.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -# Copyright 2016 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. - -source ~/.nvm/nvm.sh - -nvm use 8 -set -ex - -cd $(dirname $0)/../../.. - -base=$(pwd) - -artifacts=$base/artifacts - -mkdir -p $artifacts -cp -r $EXTERNAL_GIT_ROOT/platform={windows,linux,macos}/artifacts/node_ext_*/* $artifacts/ || true - -npm update -npm pack - -cp grpc-*.tgz $artifacts/grpc.tgz - -mkdir -p bin - -cd $base/src/node/health_check -npm pack -cp grpc-health-check-*.tgz $artifacts/ - -cd $base/src/node/tools -npm update -npm pack -cp grpc-tools-*.tgz $artifacts/ -tools_version=$(npm list | grep -oP '(?<=grpc-tools@)\S+') - -output_dir=$artifacts/grpc-precompiled-binaries/node/grpc-tools/v$tools_version -mkdir -p $output_dir - -well_known_protos=( any api compiler/plugin descriptor duration empty field_mask source_context struct timestamp type wrappers ) - -for arch in {x86,x64}; do - case $arch in - x86) - node_arch=ia32 - ;; - *) - node_arch=$arch - ;; - esac - for plat in {windows,linux,macos}; do - case $plat in - windows) - node_plat=win32 - ;; - macos) - node_plat=darwin - ;; - *) - node_plat=$plat - ;; - esac - rm -r bin/* - input_dir="$EXTERNAL_GIT_ROOT/platform=${plat}/artifacts/protoc_${plat}_${arch}" - cp $input_dir/protoc* bin/ - cp $input_dir/grpc_node_plugin* bin/ - mkdir -p bin/google/protobuf - mkdir -p bin/google/protobuf/compiler # needed for plugin.proto - for proto in "${well_known_protos[@]}"; do - cp $base/third_party/protobuf/src/google/protobuf/$proto.proto bin/google/protobuf/$proto.proto - done - tar -czf $output_dir/$node_plat-$node_arch.tar.gz bin/ - done -done diff --git a/packages/grpc-tools/CMakeLists.txt b/packages/grpc-tools/CMakeLists.txt new file mode 100644 index 000000000..60dcdb675 --- /dev/null +++ b/packages/grpc-tools/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.7) +set(CMAKE_OSX_DEPLOYMENT_TARGET "11.7" CACHE STRING "Minimum OS X deployment version" FORCE) +if(COMMAND cmake_policy) + cmake_policy(SET CMP0003 NEW) +endif(COMMAND cmake_policy) + +# MSVC runtime library flags are selected by an abstraction. +if(COMMAND cmake_policy AND POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf tests") +set(protobuf_WITH_ZLIB OFF CACHE BOOL "Build protobuf with zlib.") +set(PROTOBUF_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps/protobuf) +add_subdirectory(${PROTOBUF_ROOT_DIR}/cmake deps/protobuf) + +set(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector") + +add_executable(grpc_node_plugin + src/node_generator.cc + src/node_plugin.cc +) + +if (MSVC) + if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.15) + set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) + else () + foreach (flag_var + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif (${flag_var} MATCHES "/MD") + endforeach (flag_var) + endif () +endif (MVC) + +target_include_directories(grpc_node_plugin + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src + PRIVATE ${PROTOBUF_ROOT_DIR}/include +) + +target_link_libraries(grpc_node_plugin + libprotoc + libprotobuf +) diff --git a/packages/grpc-tools/LICENSE b/packages/grpc-tools/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/packages/grpc-tools/LICENSE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/packages/grpc-tools/README.md b/packages/grpc-tools/README.md new file mode 100644 index 000000000..eae786929 --- /dev/null +++ b/packages/grpc-tools/README.md @@ -0,0 +1,22 @@ +# grpc-tools + +This package distributes the Protocol Buffers compiler `protoc` along with the +plugin for generating client and service objects for use with the Node gRPC +libraries. + +## Usage + +This library exports the `grpc_tools_node_protoc` executable, which accepts all +of the same arguments as `protoc` itself. For use with Node, you most likely +want to use CommonJS-style imports. An example of generating code this way can +be found in [this guide](https://developers.google.com/protocol-buffers/docs/reference/javascript-generated#commonjs-imports). +The `grpc_tools_node_protoc` automatically includes the Node gRPC plugin, so +it also accepts the `--grpc_out=[option:]path` argument. The option can be +one of the following: + + - `grpc_js`: Generates code with `require('@grpc/grpc-js')` instead of + `require('grpc')` + - `generate_package_definition`: Generates code that does not `require` any + gRPC library, and instead generates `PackageDefinition` objects that can + be passed to the `loadPackageDefinition` function provided by both the + `grpc` and `@grpc/grpc-js` libraries. \ No newline at end of file diff --git a/packages/grpc-tools/bin/protoc_plugin.js b/packages/grpc-tools/bin/protoc_plugin.js index fbdafff7d..22aa03d87 100755 --- a/packages/grpc-tools/bin/protoc_plugin.js +++ b/packages/grpc-tools/bin/protoc_plugin.js @@ -32,7 +32,7 @@ var exe_ext = process.platform === 'win32' ? '.exe' : ''; var plugin = path.resolve(__dirname, 'grpc_node_plugin' + exe_ext); -var child_process = execFile(plugin, process.argv.slice(2), {encoding: 'buffer'}, function(error, stdout, stderr) { +var child_process = execFile(plugin, process.argv.slice(2), {encoding: 'buffer', maxBuffer: 1024 * 1024 * 100}, function(error, stdout, stderr) { if (error) { throw error; } diff --git a/packages/grpc-tools/build_binaries.ps1 b/packages/grpc-tools/build_binaries.ps1 new file mode 100644 index 000000000..629aea266 --- /dev/null +++ b/packages/grpc-tools/build_binaries.ps1 @@ -0,0 +1,75 @@ +<# + Copyright 2019 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. +#> + +$ErrorActionPreference = "Stop" + +<# https://stackoverflow.com/questions/16657778/install-nuget-via-powershell-script/26421187#comment107976901_48216538 #> + +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls11,Tls12' + +Install-PackageProvider -Name NuGet -RequiredVersion 2.8.5.201 -Force +Import-PackageProvider -Name NuGet -RequiredVersion 2.8.5.201 +Install-Module -Force -Name 7Zip4Powershell + +$env:Path += ";C:\Program Files\CMake\bin" + +function MkDir-p($Path) { + $FullPath = "\\?\" + $Path + if (-not (Test-Path -Path $FullPath)) { + New-Item -ItemType directory -Path $FullPath | Out-Null + } +} + +$Base = $PSScriptRoot +cd $Base +$ProtobufBase = $Base + "/deps/protobuf" +MkDir-p ($Base + "/build/bin") + +$PackageFile = $Base + "/package.json" +$ToolsVersion = ((Get-Content $PackageFile) -join "`n" | ConvertFrom-Json).version + +cd ../.. +$OutDir = $pwd.Path + "/artifacts/grpc-tools/v" + $ToolsVersion +Mkdir-p $OutDir + +cd $Base + +Set-PSDebug -trace 2 + +$Arch = $Env:ARCH + +if ($Arch -eq "x64") { + $ArchName = "x64" +} else { + $ArchName = "Win32" +} + +& cmake.exe . -A $ArchName +if ($LASTEXITCODE -ne 0) { + throw "cmake failed" +} +& cmake.exe --build . +if ($LASTEXITCODE -ne 0) { + throw "cmake build failed" +} + +Copy-Item ($ProtobufBase + "/Debug/protoc.exe") -Destination ($Base + "/build/bin/protoc.exe") +Copy-Item ($Base + "/Debug/grpc_node_plugin.exe") -Destination ($Base + "/build/bin/grpc_node_plugin.exe") + +Compress-7Zip -Path ($Base + "/build") -Format Tar -ArchiveFileName ($Base + "/Archive.tar") +Compress-7Zip -Path ($Base + "/Archive.tar") -Format GZip -ArchiveFileName ($OutDir + "/win32-" + $Arch + ".tar.gz") + +& git clean -xdf . \ No newline at end of file diff --git a/packages/grpc-tools/build_binaries.sh b/packages/grpc-tools/build_binaries.sh new file mode 100755 index 000000000..d22bbc4ff --- /dev/null +++ b/packages/grpc-tools/build_binaries.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# Copyright 2019 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. + +set -e + +uname -a + +cd $(dirname $0) +base=$(pwd) +protobuf_base=$base/deps/protobuf + +tools_version=$(jq '.version' < package.json | tr -d '"') + +# Note: artifacts should not be output in the package directory +out_dir=$base/../../artifacts/grpc-tools/v$tools_version +mkdir -p "$out_dir" + +build () { + cmake_flag=$* + + rm -rf $base/build/bin + rm -f $base/CMakeCache.txt + rm -rf $base/CMakeFiles + rm -f $protobuf_base/CMakeCache.txt + rm -rf $protobuf_base/CMakeFiles + cmake $cmake_flag . && cmake --build . --target clean && cmake --build . -- -j 12 + mkdir -p $base/build/bin + cp -L $protobuf_base/protoc $base/build/bin/protoc + cp $base/grpc_node_plugin $base/build/bin/ + file $base/build/bin/* +} + +artifacts() { + platform=$1 + arch=$2 + dir=$3 + case $(uname -s) in + Linux) + tar -czf $out_dir/$platform-$arch.tar.gz -C $(dirname $dir) $(basename $dir) + ;; + Darwin) + tar --format=gnutar -czf $out_dir/$platform-$arch.tar.gz -C $(dirname $dir) $(basename $dir) + ;; + esac +} + +case $(uname -s) in + Linux) + build -DCMAKE_TOOLCHAIN_FILE=linux_32bit.toolchain.cmake + artifacts linux ia32 $base/build/bin + build -DCMAKE_TOOLCHAIN_FILE=linux_64bit.toolchain.cmake + artifacts linux x64 $base/build/bin + ;; + Darwin) + build -DCMAKE_TOOLCHAIN_FILE=linux_64bit.toolchain.cmake -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" + + for arch in "x64" "arm64"; do + mkdir $base/build/bin/$arch + for bin in protoc grpc_node_plugin; do + lipo -extract x86_64 $base/build/bin/$bin -o $base/build/bin/$arch/$bin + otool -l $base/build/bin/$arch/$bin | grep minos + done + artifacts darwin $arch $base/build/bin/$arch/ + done + ;; +esac diff --git a/packages/grpc-tools/copy_well_known_protos.js b/packages/grpc-tools/copy_well_known_protos.js new file mode 100644 index 000000000..d95be29bb --- /dev/null +++ b/packages/grpc-tools/copy_well_known_protos.js @@ -0,0 +1,32 @@ +/* + * Copyright 2019 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. + * + */ + +'use strict'; + +const path = require('path'); +const fs = require('fs'); + +const sourceDir = path.join(__dirname, 'deps/protobuf/src/google/protobuf'); +const destDir = path.join(__dirname, 'bin/google/protobuf'); + +fs.mkdirSync(path.join(destDir, 'compiler'), {recursive: true}); + +const wellKnownProtos=['any', 'api', 'compiler/plugin', 'descriptor', 'duration', 'empty', + 'field_mask', 'source_context', 'struct', 'timestamp', 'type', 'wrappers']; +for (const proto of wellKnownProtos) { + fs.copyFileSync(path.join(sourceDir, proto + '.proto'), path.join(destDir, proto + '.proto')); +} \ No newline at end of file diff --git a/packages/grpc-tools/deps/protobuf b/packages/grpc-tools/deps/protobuf new file mode 160000 index 000000000..7c40b2df1 --- /dev/null +++ b/packages/grpc-tools/deps/protobuf @@ -0,0 +1 @@ +Subproject commit 7c40b2df1fdf6f414c1c18c789715a9c948a0725 diff --git a/packages/grpc-tools/linux_32bit.toolchain.cmake b/packages/grpc-tools/linux_32bit.toolchain.cmake new file mode 100644 index 000000000..5a1b80f03 --- /dev/null +++ b/packages/grpc-tools/linux_32bit.toolchain.cmake @@ -0,0 +1,3 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32" CACHE STRING "c++ flags") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32" CACHE STRING "c flags") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32" CACHE STRING "ld flags") \ No newline at end of file diff --git a/packages/grpc-tools/linux_64bit.toolchain.cmake b/packages/grpc-tools/linux_64bit.toolchain.cmake new file mode 100644 index 000000000..7a9b79a45 --- /dev/null +++ b/packages/grpc-tools/linux_64bit.toolchain.cmake @@ -0,0 +1,3 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64" CACHE STRING "c++ flags") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64" CACHE STRING "c flags") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m64" CACHE STRING "ld flags") \ No newline at end of file diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index c1062c10a..d4ac74ea1 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -1,6 +1,6 @@ { "name": "grpc-tools", - "version": "1.7.0-pre1", + "version": "1.12.3", "author": "Google Inc.", "description": "Tools for developing with gRPC on Node.js", "homepage": "https://grpc.io/", @@ -20,13 +20,16 @@ "grpc_tools_node_protoc_plugin": "./bin/protoc_plugin.js" }, "scripts": { - "install": "./node_modules/.bin/node-pre-gyp install" + "install": "node-pre-gyp install", + "prepublishOnly": "git submodule update --init --recursive && node copy_well_known_protos.js" + }, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.5" }, - "bundledDependencies": ["node-pre-gyp"], "binary": { "module_name": "grpc_tools", - "host": "https://storage.googleapis.com/", - "remote_path": "grpc-precompiled-binaries/node/{name}/v{version}", + "host": "https://node-precompiled-binaries.grpc.io/", + "remote_path": "{name}/v{version}", "package_name": "{platform}-{arch}.tar.gz", "module_path": "bin" }, diff --git a/packages/grpc-tools/src/config.h b/packages/grpc-tools/src/config.h new file mode 100644 index 000000000..7c7cc5d18 --- /dev/null +++ b/packages/grpc-tools/src/config.h @@ -0,0 +1,91 @@ +/* + * + * 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. + * + */ + +#ifndef SRC_COMPILER_CONFIG_H +#define SRC_COMPILER_CONFIG_H + +#include "config_protobuf.h" + +#ifndef GRPC_CUSTOM_CODEGENERATOR +#include +#define GRPC_CUSTOM_CODEGENERATOR ::google::protobuf::compiler::CodeGenerator +#define GRPC_CUSTOM_GENERATORCONTEXT \ + ::google::protobuf::compiler::GeneratorContext +#endif + +#ifndef GRPC_CUSTOM_PRINTER +#include +#include +#include +#define GRPC_CUSTOM_PRINTER ::google::protobuf::io::Printer +#define GRPC_CUSTOM_CODEDOUTPUTSTREAM ::google::protobuf::io::CodedOutputStream +#define GRPC_CUSTOM_STRINGOUTPUTSTREAM \ + ::google::protobuf::io::StringOutputStream +#endif + +#ifndef GRPC_CUSTOM_PLUGINMAIN +#include +#define GRPC_CUSTOM_PLUGINMAIN ::google::protobuf::compiler::PluginMain +#endif + +#ifndef GRPC_CUSTOM_PARSEGENERATORPARAMETER +#include +#define GRPC_CUSTOM_PARSEGENERATORPARAMETER \ + ::google::protobuf::compiler::ParseGeneratorParameter +#endif + +#ifndef GRPC_CUSTOM_STRING +#include +#define GRPC_CUSTOM_STRING std::string +#endif + +namespace grpc { + +typedef GRPC_CUSTOM_STRING string; + +namespace protobuf { + +namespace compiler { +typedef GRPC_CUSTOM_CODEGENERATOR CodeGenerator; +typedef GRPC_CUSTOM_GENERATORCONTEXT GeneratorContext; +static inline int PluginMain(int argc, char* argv[], + const CodeGenerator* generator) { + return GRPC_CUSTOM_PLUGINMAIN(argc, argv, generator); +} +static inline void ParseGeneratorParameter( + const string& parameter, std::vector >* options) { + GRPC_CUSTOM_PARSEGENERATORPARAMETER(parameter, options); +} + +} // namespace compiler +namespace io { +typedef GRPC_CUSTOM_PRINTER Printer; +typedef GRPC_CUSTOM_CODEDOUTPUTSTREAM CodedOutputStream; +typedef GRPC_CUSTOM_STRINGOUTPUTSTREAM StringOutputStream; +} // namespace io +} // namespace protobuf +} // namespace grpc + +namespace grpc_cpp_generator { + +static const char* const kCppGeneratorMessageHeaderExt = ".pb.h"; +static const char* const kCppGeneratorServiceHeaderExt = ".grpc.pb.h"; + +} // namespace grpc_cpp_generator + +#endif // SRC_COMPILER_CONFIG_H diff --git a/packages/grpc-tools/src/config_protobuf.h b/packages/grpc-tools/src/config_protobuf.h new file mode 100644 index 000000000..94e593d1e --- /dev/null +++ b/packages/grpc-tools/src/config_protobuf.h @@ -0,0 +1,95 @@ +/* + * + * 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. + * + */ + +#ifndef GRPCPP_IMPL_CODEGEN_CONFIG_PROTOBUF_H +#define GRPCPP_IMPL_CODEGEN_CONFIG_PROTOBUF_H + +#define GRPC_OPEN_SOURCE_PROTO + +#ifndef GRPC_CUSTOM_PROTOBUF_INT64 +#include +#define GRPC_CUSTOM_PROTOBUF_INT64 ::google::protobuf::int64 +#endif + +#ifndef GRPC_CUSTOM_MESSAGE +#ifdef GRPC_USE_PROTO_LITE +#include +#define GRPC_CUSTOM_MESSAGE ::google::protobuf::MessageLite +#else +#include +#define GRPC_CUSTOM_MESSAGE ::google::protobuf::Message +#endif +#endif + +#ifndef GRPC_CUSTOM_DESCRIPTOR +#include +#include +#define GRPC_CUSTOM_DESCRIPTOR ::google::protobuf::Descriptor +#define GRPC_CUSTOM_DESCRIPTORPOOL ::google::protobuf::DescriptorPool +#define GRPC_CUSTOM_FIELDDESCRIPTOR ::google::protobuf::FieldDescriptor +#define GRPC_CUSTOM_FILEDESCRIPTOR ::google::protobuf::FileDescriptor +#define GRPC_CUSTOM_FILEDESCRIPTORPROTO ::google::protobuf::FileDescriptorProto +#define GRPC_CUSTOM_METHODDESCRIPTOR ::google::protobuf::MethodDescriptor +#define GRPC_CUSTOM_SERVICEDESCRIPTOR ::google::protobuf::ServiceDescriptor +#define GRPC_CUSTOM_SOURCELOCATION ::google::protobuf::SourceLocation +#endif + +#ifndef GRPC_CUSTOM_DESCRIPTORDATABASE +#include +#define GRPC_CUSTOM_DESCRIPTORDATABASE ::google::protobuf::DescriptorDatabase +#define GRPC_CUSTOM_SIMPLEDESCRIPTORDATABASE \ + ::google::protobuf::SimpleDescriptorDatabase +#endif + +#ifndef GRPC_CUSTOM_ZEROCOPYOUTPUTSTREAM +#include +#include +#define GRPC_CUSTOM_ZEROCOPYOUTPUTSTREAM \ + ::google::protobuf::io::ZeroCopyOutputStream +#define GRPC_CUSTOM_ZEROCOPYINPUTSTREAM \ + ::google::protobuf::io::ZeroCopyInputStream +#define GRPC_CUSTOM_CODEDINPUTSTREAM ::google::protobuf::io::CodedInputStream +#endif + +namespace grpc { +namespace protobuf { + +typedef GRPC_CUSTOM_MESSAGE Message; +typedef GRPC_CUSTOM_PROTOBUF_INT64 int64; + +typedef GRPC_CUSTOM_DESCRIPTOR Descriptor; +typedef GRPC_CUSTOM_DESCRIPTORPOOL DescriptorPool; +typedef GRPC_CUSTOM_DESCRIPTORDATABASE DescriptorDatabase; +typedef GRPC_CUSTOM_FIELDDESCRIPTOR FieldDescriptor; +typedef GRPC_CUSTOM_FILEDESCRIPTOR FileDescriptor; +typedef GRPC_CUSTOM_FILEDESCRIPTORPROTO FileDescriptorProto; +typedef GRPC_CUSTOM_METHODDESCRIPTOR MethodDescriptor; +typedef GRPC_CUSTOM_SERVICEDESCRIPTOR ServiceDescriptor; +typedef GRPC_CUSTOM_SIMPLEDESCRIPTORDATABASE SimpleDescriptorDatabase; +typedef GRPC_CUSTOM_SOURCELOCATION SourceLocation; + +namespace io { +typedef GRPC_CUSTOM_ZEROCOPYOUTPUTSTREAM ZeroCopyOutputStream; +typedef GRPC_CUSTOM_ZEROCOPYINPUTSTREAM ZeroCopyInputStream; +typedef GRPC_CUSTOM_CODEDINPUTSTREAM CodedInputStream; +} // namespace io + +} // namespace protobuf +} // namespace grpc + +#endif // GRPCPP_IMPL_CODEGEN_CONFIG_PROTOBUF_H diff --git a/packages/grpc-tools/src/generator_helpers.h b/packages/grpc-tools/src/generator_helpers.h new file mode 100644 index 000000000..42959a082 --- /dev/null +++ b/packages/grpc-tools/src/generator_helpers.h @@ -0,0 +1,277 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_INTERNAL_COMPILER_GENERATOR_HELPERS_H +#define GRPC_INTERNAL_COMPILER_GENERATOR_HELPERS_H + +#include +#include +#include +#include +#include + +#include "config.h" + +namespace grpc_generator { + +inline bool StripSuffix(grpc::string* filename, const grpc::string& suffix) { + if (filename->length() >= suffix.length()) { + size_t suffix_pos = filename->length() - suffix.length(); + if (filename->compare(suffix_pos, grpc::string::npos, suffix) == 0) { + filename->resize(filename->size() - suffix.size()); + return true; + } + } + + return false; +} + +inline bool StripPrefix(grpc::string* name, const grpc::string& prefix) { + if (name->length() >= prefix.length()) { + if (name->substr(0, prefix.size()) == prefix) { + *name = name->substr(prefix.size()); + return true; + } + } + return false; +} + +inline grpc::string StripProto(grpc::string filename) { + if (!StripSuffix(&filename, ".protodevel")) { + StripSuffix(&filename, ".proto"); + } + return filename; +} + +inline grpc::string StringReplace(grpc::string str, const grpc::string& from, + const grpc::string& to, bool replace_all) { + size_t pos = 0; + + do { + pos = str.find(from, pos); + if (pos == grpc::string::npos) { + break; + } + str.replace(pos, from.length(), to); + pos += to.length(); + } while (replace_all); + + return str; +} + +inline grpc::string StringReplace(grpc::string str, const grpc::string& from, + const grpc::string& to) { + return StringReplace(str, from, to, true); +} + +inline std::vector tokenize(const grpc::string& input, + const grpc::string& delimiters) { + std::vector tokens; + size_t pos, last_pos = 0; + + for (;;) { + bool done = false; + pos = input.find_first_of(delimiters, last_pos); + if (pos == grpc::string::npos) { + done = true; + pos = input.length(); + } + + tokens.push_back(input.substr(last_pos, pos - last_pos)); + if (done) return tokens; + + last_pos = pos + 1; + } +} + +inline grpc::string CapitalizeFirstLetter(grpc::string s) { + if (s.empty()) { + return s; + } + s[0] = ::toupper(s[0]); + return s; +} + +inline grpc::string LowercaseFirstLetter(grpc::string s) { + if (s.empty()) { + return s; + } + s[0] = ::tolower(s[0]); + return s; +} + +inline grpc::string LowerUnderscoreToUpperCamel(grpc::string str) { + std::vector tokens = tokenize(str, "_"); + grpc::string result = ""; + for (unsigned int i = 0; i < tokens.size(); i++) { + result += CapitalizeFirstLetter(tokens[i]); + } + return result; +} + +inline grpc::string FileNameInUpperCamel( + const grpc::protobuf::FileDescriptor* file, bool include_package_path) { + std::vector tokens = tokenize(StripProto(file->name()), "/"); + grpc::string result = ""; + if (include_package_path) { + for (unsigned int i = 0; i < tokens.size() - 1; i++) { + result += tokens[i] + "/"; + } + } + result += LowerUnderscoreToUpperCamel(tokens.back()); + return result; +} + +inline grpc::string FileNameInUpperCamel( + const grpc::protobuf::FileDescriptor* file) { + return FileNameInUpperCamel(file, true); +} + +enum MethodType { + METHODTYPE_NO_STREAMING, + METHODTYPE_CLIENT_STREAMING, + METHODTYPE_SERVER_STREAMING, + METHODTYPE_BIDI_STREAMING +}; + +inline MethodType GetMethodType( + const grpc::protobuf::MethodDescriptor* method) { + if (method->client_streaming()) { + if (method->server_streaming()) { + return METHODTYPE_BIDI_STREAMING; + } else { + return METHODTYPE_CLIENT_STREAMING; + } + } else { + if (method->server_streaming()) { + return METHODTYPE_SERVER_STREAMING; + } else { + return METHODTYPE_NO_STREAMING; + } + } +} + +inline void Split(const grpc::string& s, char delim, + std::vector* append_to) { + std::istringstream iss(s); + grpc::string piece; + while (std::getline(iss, piece)) { + append_to->push_back(piece); + } +} + +enum CommentType { + COMMENTTYPE_LEADING, + COMMENTTYPE_TRAILING, + COMMENTTYPE_LEADING_DETACHED +}; + +// Get all the raw comments and append each line without newline to out. +template +inline void GetComment(const DescriptorType* desc, CommentType type, + std::vector* out) { + grpc::protobuf::SourceLocation location; + if (!desc->GetSourceLocation(&location)) { + return; + } + if (type == COMMENTTYPE_LEADING || type == COMMENTTYPE_TRAILING) { + const grpc::string& comments = type == COMMENTTYPE_LEADING + ? location.leading_comments + : location.trailing_comments; + Split(comments, '\n', out); + } else if (type == COMMENTTYPE_LEADING_DETACHED) { + for (unsigned int i = 0; i < location.leading_detached_comments.size(); + i++) { + Split(location.leading_detached_comments[i], '\n', out); + out->push_back(""); + } + } else { + std::cerr << "Unknown comment type " << type << std::endl; + abort(); + } +} + +// Each raw comment line without newline is appended to out. +// For file level leading and detached leading comments, we return comments +// above syntax line. Return nothing for trailing comments. +template <> +inline void GetComment(const grpc::protobuf::FileDescriptor* desc, + CommentType type, std::vector* out) { + if (type == COMMENTTYPE_TRAILING) { + return; + } + grpc::protobuf::SourceLocation location; + std::vector path; + path.push_back(grpc::protobuf::FileDescriptorProto::kSyntaxFieldNumber); + if (!desc->GetSourceLocation(path, &location)) { + return; + } + if (type == COMMENTTYPE_LEADING) { + Split(location.leading_comments, '\n', out); + } else if (type == COMMENTTYPE_LEADING_DETACHED) { + for (unsigned int i = 0; i < location.leading_detached_comments.size(); + i++) { + Split(location.leading_detached_comments[i], '\n', out); + out->push_back(""); + } + } else { + std::cerr << "Unknown comment type " << type << std::endl; + abort(); + } +} + +// Add prefix and newline to each comment line and concatenate them together. +// Make sure there is a space after the prefix unless the line is empty. +inline grpc::string GenerateCommentsWithPrefix( + const std::vector& in, const grpc::string& prefix) { + std::ostringstream oss; + for (auto it = in.begin(); it != in.end(); it++) { + const grpc::string& elem = *it; + if (elem.empty()) { + oss << prefix << "\n"; + } else if (elem[0] == ' ') { + oss << prefix << elem << "\n"; + } else { + oss << prefix << " " << elem << "\n"; + } + } + return oss.str(); +} + +template +inline grpc::string GetPrefixedComments(const DescriptorType* desc, + bool leading, + const grpc::string& prefix) { + std::vector out; + if (leading) { + grpc_generator::GetComment( + desc, grpc_generator::COMMENTTYPE_LEADING_DETACHED, &out); + std::vector leading; + grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_LEADING, + &leading); + out.insert(out.end(), leading.begin(), leading.end()); + } else { + grpc_generator::GetComment(desc, grpc_generator::COMMENTTYPE_TRAILING, + &out); + } + return GenerateCommentsWithPrefix(out, prefix); +} + +} // namespace grpc_generator + +#endif // GRPC_INTERNAL_COMPILER_GENERATOR_HELPERS_H diff --git a/packages/grpc-tools/src/node_generator.cc b/packages/grpc-tools/src/node_generator.cc new file mode 100644 index 000000000..b4aad0e31 --- /dev/null +++ b/packages/grpc-tools/src/node_generator.cc @@ -0,0 +1,285 @@ +/* + * + * Copyright 2016 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. + * + */ + +#include + +#include "config.h" +#include "generator_helpers.h" +#include "node_generator.h" +#include "node_generator_helpers.h" + +using grpc::protobuf::Descriptor; +using grpc::protobuf::FileDescriptor; +using grpc::protobuf::MethodDescriptor; +using grpc::protobuf::ServiceDescriptor; +using grpc::protobuf::io::Printer; +using grpc::protobuf::io::StringOutputStream; +using std::map; + +namespace grpc_node_generator { +namespace { + +// Returns the alias we assign to the module of the given .proto filename +// when importing. Copied entirely from +// github:google/protobuf/src/google/protobuf/compiler/js/js_generator.cc#L154 +grpc::string ModuleAlias(const grpc::string filename) { + // This scheme could technically cause problems if a file includes any 2 of: + // foo/bar_baz.proto + // foo_bar_baz.proto + // foo_bar/baz.proto + // + // We'll worry about this problem if/when we actually see it. This name isn't + // exposed to users so we can change it later if we need to. + grpc::string basename = grpc_generator::StripProto(filename); + basename = grpc_generator::StringReplace(basename, "-", "$"); + basename = grpc_generator::StringReplace(basename, "/", "_"); + basename = grpc_generator::StringReplace(basename, ".", "_"); + return basename + "_pb"; +} + +// Given a filename like foo/bar/baz.proto, returns the corresponding JavaScript +// message file foo/bar/baz.js +grpc::string GetJSMessageFilename(const grpc::string& filename) { + grpc::string name = filename; + return grpc_generator::StripProto(name) + "_pb.js"; +} + +// Given a filename like foo/bar/baz.proto, returns the root directory +// path ../../ +grpc::string GetRootPath(const grpc::string& from_filename, + const grpc::string& to_filename) { + if (to_filename.find("google/protobuf") == 0) { + // Well-known types (.proto files in the google/protobuf directory) are + // assumed to come from the 'google-protobuf' npm package. We may want to + // generalize this exception later by letting others put generated code in + // their own npm packages. + return "google-protobuf/"; + } + size_t slashes = std::count(from_filename.begin(), from_filename.end(), '/'); + if (slashes == 0) { + return "./"; + } + grpc::string result = ""; + for (size_t i = 0; i < slashes; i++) { + result += "../"; + } + return result; +} + +// Return the relative path to load to_file from the directory containing +// from_file, assuming that both paths are relative to the same directory +grpc::string GetRelativePath(const grpc::string& from_file, + const grpc::string& to_file) { + return GetRootPath(from_file, to_file) + to_file; +} + +/* Finds all message types used in all services in the file, and returns them + * as a map of fully qualified message type name to message descriptor */ +map GetAllMessages( + const FileDescriptor* file) { + map message_types; + for (int service_num = 0; service_num < file->service_count(); + service_num++) { + const ServiceDescriptor* service = file->service(service_num); + for (int method_num = 0; method_num < service->method_count(); + method_num++) { + const MethodDescriptor* method = service->method(method_num); + const Descriptor* input_type = method->input_type(); + const Descriptor* output_type = method->output_type(); + message_types[input_type->full_name()] = input_type; + message_types[output_type->full_name()] = output_type; + } + } + return message_types; +} + +grpc::string MessageIdentifierName(const grpc::string& name) { + return grpc_generator::StringReplace(name, ".", "_"); +} + +grpc::string NodeObjectPath(const Descriptor* descriptor) { + grpc::string module_alias = ModuleAlias(descriptor->file()->name()); + grpc::string name = descriptor->full_name(); + grpc_generator::StripPrefix(&name, descriptor->file()->package() + "."); + return module_alias + "." + name; +} + +// Prints out the message serializer and deserializer functions +void PrintMessageTransformer(const Descriptor* descriptor, Printer* out) { + map template_vars; + grpc::string full_name = descriptor->full_name(); + template_vars["identifier_name"] = MessageIdentifierName(full_name); + template_vars["name"] = full_name; + template_vars["node_name"] = NodeObjectPath(descriptor); + // Print the serializer + out->Print(template_vars, "function serialize_$identifier_name$(arg) {\n"); + out->Indent(); + out->Print(template_vars, "if (!(arg instanceof $node_name$)) {\n"); + out->Indent(); + out->Print(template_vars, + "throw new Error('Expected argument of type $name$');\n"); + out->Outdent(); + out->Print("}\n"); + out->Print("return Buffer.from(arg.serializeBinary());\n"); + out->Outdent(); + out->Print("}\n\n"); + + // Print the deserializer + out->Print(template_vars, + "function deserialize_$identifier_name$(buffer_arg) {\n"); + out->Indent(); + out->Print( + template_vars, + "return $node_name$.deserializeBinary(new Uint8Array(buffer_arg));\n"); + out->Outdent(); + out->Print("}\n\n"); +} + +void PrintMethod(const MethodDescriptor* method, Printer* out) { + const Descriptor* input_type = method->input_type(); + const Descriptor* output_type = method->output_type(); + map vars; + vars["service_name"] = method->service()->full_name(); + vars["name"] = method->name(); + vars["input_type"] = NodeObjectPath(input_type); + vars["input_type_id"] = MessageIdentifierName(input_type->full_name()); + vars["output_type"] = NodeObjectPath(output_type); + vars["output_type_id"] = MessageIdentifierName(output_type->full_name()); + vars["client_stream"] = method->client_streaming() ? "true" : "false"; + vars["server_stream"] = method->server_streaming() ? "true" : "false"; + out->Print("{\n"); + out->Indent(); + out->Print(vars, "path: '/$service_name$/$name$',\n"); + out->Print(vars, "requestStream: $client_stream$,\n"); + out->Print(vars, "responseStream: $server_stream$,\n"); + out->Print(vars, "requestType: $input_type$,\n"); + out->Print(vars, "responseType: $output_type$,\n"); + out->Print(vars, "requestSerialize: serialize_$input_type_id$,\n"); + out->Print(vars, "requestDeserialize: deserialize_$input_type_id$,\n"); + out->Print(vars, "responseSerialize: serialize_$output_type_id$,\n"); + out->Print(vars, "responseDeserialize: deserialize_$output_type_id$,\n"); + out->Outdent(); + out->Print("}"); +} + +// Prints out the service descriptor object +void PrintService(const ServiceDescriptor* service, Printer* out, + const Parameters& params) { + map template_vars; + out->PrintRaw(GetNodeComments(service, true).c_str()); + template_vars["name"] = service->name(); + template_vars["full_name"] = service->full_name(); + if (params.generate_package_definition) { + out->Print(template_vars, "var $name$Service = exports['$full_name$'] = {\n"); + } else { + out->Print(template_vars, "var $name$Service = exports.$name$Service = {\n"); + } + out->Indent(); + for (int i = 0; i < service->method_count(); i++) { + grpc::string method_name = + grpc_generator::LowercaseFirstLetter(service->method(i)->name()); + out->PrintRaw(GetNodeComments(service->method(i), true).c_str()); + out->Print("$method_name$: ", "method_name", method_name); + PrintMethod(service->method(i), out); + out->Print(",\n"); + out->PrintRaw(GetNodeComments(service->method(i), false).c_str()); + } + out->Outdent(); + out->Print("};\n\n"); + if (!params.generate_package_definition) { + out->Print(template_vars, + "exports.$name$Client = " + "grpc.makeGenericClientConstructor($name$Service);\n"); + } + out->PrintRaw(GetNodeComments(service, false).c_str()); +} + +void PrintImports(const FileDescriptor* file, Printer* out, + const Parameters& params) { + if (!params.generate_package_definition) { + grpc::string package = params.grpc_js ? "@grpc/grpc-js" : "grpc"; + out->Print("var grpc = require('$package$');\n", "package", package); + } + if (file->message_type_count() > 0) { + grpc::string file_path = + GetRelativePath(file->name(), GetJSMessageFilename(file->name())); + out->Print("var $module_alias$ = require('$file_path$');\n", "module_alias", + ModuleAlias(file->name()), "file_path", file_path); + } + + for (int i = 0; i < file->dependency_count(); i++) { + grpc::string file_path = GetRelativePath( + file->name(), GetJSMessageFilename(file->dependency(i)->name())); + out->Print("var $module_alias$ = require('$file_path$');\n", "module_alias", + ModuleAlias(file->dependency(i)->name()), "file_path", + file_path); + } + out->Print("\n"); +} + +void PrintTransformers(const FileDescriptor* file, Printer* out) { + map messages = GetAllMessages(file); + for (std::map::iterator it = + messages.begin(); + it != messages.end(); it++) { + PrintMessageTransformer(it->second, out); + } + out->Print("\n"); +} + +void PrintServices(const FileDescriptor* file, Printer* out, + const Parameters& params) { + for (int i = 0; i < file->service_count(); i++) { + PrintService(file->service(i), out, params); + } +} +} // namespace + +grpc::string GenerateFile(const FileDescriptor* file, + const Parameters& params) { + grpc::string output; + { + StringOutputStream output_stream(&output); + Printer out(&output_stream, '$'); + + if (file->service_count() == 0) { + output = "// GENERATED CODE -- NO SERVICES IN PROTO"; + return output; + } + out.Print("// GENERATED CODE -- DO NOT EDIT!\n\n"); + + grpc::string leading_comments = GetNodeComments(file, true); + if (!leading_comments.empty()) { + out.Print("// Original file comments:\n"); + out.PrintRaw(leading_comments.c_str()); + } + + out.Print("'use strict';\n"); + + PrintImports(file, &out, params); + + PrintTransformers(file, &out); + + PrintServices(file, &out, params); + + out.PrintRaw(GetNodeComments(file, false).c_str()); + } + return output; +} + +} // namespace grpc_node_generator diff --git a/packages/grpc-native-core/ext/slice.h b/packages/grpc-tools/src/node_generator.h similarity index 53% rename from packages/grpc-native-core/ext/slice.h rename to packages/grpc-tools/src/node_generator.h index 0a652c575..a88d54a93 100644 --- a/packages/grpc-native-core/ext/slice.h +++ b/packages/grpc-tools/src/node_generator.h @@ -16,23 +16,23 @@ * */ -#include -#include -#include +#ifndef GRPC_INTERNAL_COMPILER_NODE_GENERATOR_H +#define GRPC_INTERNAL_COMPILER_NODE_GENERATOR_H -namespace grpc { -namespace node { +#include "config.h" -typedef Nan::Persistent> - PersistentValue; +namespace grpc_node_generator { -grpc_slice CreateSliceFromString(const v8::Local source); +struct Parameters { + // Generate a package definition object instead of Client classes + bool generate_package_definition; + // Use pure JavaScript gRPC Client + bool grpc_js; +}; -grpc_slice CreateSliceFromBuffer(const v8::Local source); +grpc::string GenerateFile(const grpc::protobuf::FileDescriptor* file, + const Parameters& params); -v8::Local CopyStringFromSlice(const grpc_slice slice); +} // namespace grpc_node_generator -v8::Local CreateBufferFromSlice(const grpc_slice slice); - -} // namespace node -} // namespace grpc +#endif // GRPC_INTERNAL_COMPILER_NODE_GENERATOR_H diff --git a/packages/grpc-tools/src/node_generator_helpers.h b/packages/grpc-tools/src/node_generator_helpers.h new file mode 100644 index 000000000..191298482 --- /dev/null +++ b/packages/grpc-tools/src/node_generator_helpers.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2016 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. + * + */ + +#ifndef GRPC_INTERNAL_COMPILER_NODE_GENERATOR_HELPERS_H +#define GRPC_INTERNAL_COMPILER_NODE_GENERATOR_HELPERS_H + +#include + +#include "config.h" +#include "generator_helpers.h" + +namespace grpc_node_generator { + +inline grpc::string GetJSServiceFilename(const grpc::string& filename) { + return grpc_generator::StripProto(filename) + "_grpc_pb.js"; +} + +// Get leading or trailing comments in a string. Comment lines start with "// ". +// Leading detached comments are put in in front of leading comments. +template +inline grpc::string GetNodeComments(const DescriptorType* desc, bool leading) { + return grpc_generator::GetPrefixedComments(desc, leading, "//"); +} + +} // namespace grpc_node_generator + +#endif // GRPC_INTERNAL_COMPILER_NODE_GENERATOR_HELPERS_H diff --git a/packages/grpc-tools/src/node_plugin.cc b/packages/grpc-tools/src/node_plugin.cc new file mode 100644 index 000000000..b847bcaba --- /dev/null +++ b/packages/grpc-tools/src/node_plugin.cc @@ -0,0 +1,77 @@ +/* + * + * 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. + * + */ + +// Generates Node gRPC service interface out of Protobuf IDL. + +#include + +#include "config.h" +#include "node_generator.h" +#include "node_generator_helpers.h" + +using grpc_node_generator::GenerateFile; +using grpc_node_generator::GetJSServiceFilename; + +class NodeGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { + public: + NodeGrpcGenerator() {} + ~NodeGrpcGenerator() {} + + bool Generate(const grpc::protobuf::FileDescriptor* file, + const grpc::string& parameter, + grpc::protobuf::compiler::GeneratorContext* context, + grpc::string* error) const { + grpc_node_generator::Parameters generator_parameters; + generator_parameters.generate_package_definition = false; + generator_parameters.grpc_js = false; + if (!parameter.empty()) { + std::vector parameters_list = + grpc_generator::tokenize(parameter, ","); + for (auto parameter_string = parameters_list.begin(); + parameter_string != parameters_list.end(); parameter_string++) { + if (*parameter_string == "generate_package_definition") { + generator_parameters.generate_package_definition = true; + } else if (*parameter_string == "grpc_js") { + generator_parameters.grpc_js = true; + } + } + } + grpc::string code = GenerateFile(file, generator_parameters); + if (code.size() == 0) { + return true; + } + + // Get output file name + grpc::string file_name = GetJSServiceFilename(file->name()); + + std::unique_ptr output( + context->Open(file_name)); + grpc::protobuf::io::CodedOutputStream coded_out(output.get()); + coded_out.WriteRaw(code.data(), code.size()); + return true; + } + + uint64_t GetSupportedFeatures() const override { + return FEATURE_PROTO3_OPTIONAL; + } +}; + +int main(int argc, char* argv[]) { + NodeGrpcGenerator generator; + return grpc::protobuf::compiler::PluginMain(argc, argv, &generator); +} diff --git a/packages/proto-loader/LICENSE b/packages/proto-loader/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/packages/proto-loader/LICENSE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/packages/proto-loader/README.md b/packages/proto-loader/README.md new file mode 100644 index 000000000..2a7af61e3 --- /dev/null +++ b/packages/proto-loader/README.md @@ -0,0 +1,130 @@ +# gRPC Protobuf Loader + +A utility package for loading `.proto` files for use with gRPC, using the latest Protobuf.js package. +Please refer to [protobuf.js' documentation](https://github.com/dcodeIO/protobuf.js/blob/master/README.md) +to understands its features and limitations. + +## Installation + +```sh +npm install @grpc/proto-loader +``` + +## Usage + +```js +const protoLoader = require('@grpc/proto-loader'); +const grpcLibrary = require('grpc'); +// OR +const grpcLibrary = require('@grpc/grpc-js'); + +protoLoader.load(protoFileName, options).then(packageDefinition => { + const packageObject = grpcLibrary.loadPackageDefinition(packageDefinition); +}); +// OR +const packageDefinition = protoLoader.loadSync(protoFileName, options); +const packageObject = grpcLibrary.loadPackageDefinition(packageDefinition); +``` + +The options parameter is an object that can have the following optional properties: + +| Field name | Valid values | Description +|------------|--------------|------------ +| `keepCase` | `true` or `false` | Preserve field names. The default is to change them to camel case. +| `longs` | `String` or `Number` | The type to use to represent `long` values. Defaults to a `Long` object type. +| `enums` | `String` | The type to use to represent `enum` values. Defaults to the numeric value. +| `bytes` | `Array` or `String` | The type to use to represent `bytes` values. Defaults to `Buffer`. +| `defaults` | `true` or `false` | Set default values on output objects. Defaults to `false`. +| `arrays` | `true` or `false` | Set empty arrays for missing array values even if `defaults` is `false` Defaults to `false`. +| `objects` | `true` or `false` | Set empty objects for missing object values even if `defaults` is `false` Defaults to `false`. +| `oneofs` | `true` or `false` | Set virtual oneof properties to the present field's name. Defaults to `false`. +| `json` | `true` or `false` | Represent `Infinity` and `NaN` as strings in `float` fields, and automatically decode `google.protobuf.Any` values. Defaults to `false` +| `includeDirs` | An array of strings | A list of search paths for imported `.proto` files. + +The following options object closely approximates the existing behavior of `grpc.load`: + +```js +const options = { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true +} +``` + +## Generating TypeScript types + +The `proto-loader-gen-types` script distributed with this package can be used to generate TypeScript type information for the objects loaded at runtime. More information about how to use it can be found in [the *@grpc/proto-loader TypeScript Type Generator CLI Tool* proposal document](https://github.com/grpc/proposal/blob/master/L70-node-proto-loader-type-generator.md). The arguments mostly match the `load` function's options; the full usage information is as follows: + +```console +proto-loader-gen-types.js [options] filenames... + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --keepCase Preserve the case of field names + [boolean] [default: false] + --longs The type that should be used to output 64 bit integer + values. Can be String, Number[string] [default: "Long"] + --enums The type that should be used to output enum fields. Can + be String [string] [default: "number"] + --bytes The type that should be used to output bytes fields. + Can be String, Array [string] [default: "Buffer"] + --defaults Output default values for omitted fields + [boolean] [default: false] + --arrays Output default values for omitted repeated fields even + if --defaults is not set [boolean] [default: false] + --objects Output default values for omitted message fields even + if --defaults is not set [boolean] [default: false] + --oneofs Output virtual oneof fields set to the present field's + name [boolean] [default: false] + --json Represent Infinity and NaN as strings in float fields. + Also decode google.protobuf.Any automatically + [boolean] [default: false] + --includeComments Generate doc comments from comments in the original + files [boolean] [default: false] + -I, --includeDirs Directories to search for included files [array] + -O, --outDir Directory in which to output files [string] [required] + --grpcLib The gRPC implementation library that these types will + be used with [string] [required] + --inputTemplate Template for mapping input or "permissive" type names + [string] [default: "%s"] + --outputTemplate Template for mapping output or "restricted" type names + [string] [default: "%s__Output"] + --inputBranded Output property for branded type for "permissive" + types with fullName of the Message as its value + [boolean] [default: false] + --outputBranded Output property for branded type for "restricted" + types with fullName of the Message as its value + [boolean] [default: false] +``` + +### Example Usage + +Generate the types: + +```sh +$(npm bin)/proto-loader-gen-types --longs=String --enums=String --defaults --oneofs --grpcLib=@grpc/grpc-js --outDir=proto/ proto/*.proto +``` + +Consume the types: + +```ts +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; +import { ProtoGrpcType } from './proto/example'; +import { ExampleHandlers } from './proto/example_package/Example'; + +const exampleServer: ExampleHandlers = { + // server handlers implementation... +}; + +const packageDefinition = protoLoader.loadSync('./proto/example.proto'); +const proto = (grpc.loadPackageDefinition( + packageDefinition +) as unknown) as ProtoGrpcType; + +const server = new grpc.Server(); +server.addService(proto.example_package.Example.service, exampleServer); +``` diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts new file mode 100644 index 000000000..f75822084 --- /dev/null +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -0,0 +1,920 @@ +#!/usr/bin/env node +/** + * @license + * Copyright 2020 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. + * + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +import * as Protobuf from 'protobufjs'; +import * as yargs from 'yargs'; + +import camelCase = require('lodash.camelcase'); +import { loadProtosWithOptions, addCommonProtos } from '../src/util'; + +const templateStr = "%s"; +const useNameFmter = ({outputTemplate, inputTemplate}: GeneratorOptions) => { + if (outputTemplate === inputTemplate) { + throw new Error('inputTemplate and outputTemplate must differ') + } + return { + outputName: (n: string) => outputTemplate.replace(templateStr, n), + inputName: (n: string) => inputTemplate.replace(templateStr, n) + }; +} + +type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { + includeDirs?: string[]; + grpcLib: string; + outDir: string; + verbose?: boolean; + includeComments?: boolean; + inputTemplate: string; + outputTemplate: string; + inputBranded: boolean; + outputBranded: boolean; +} + +class TextFormatter { + private readonly indentText = ' '; + private indentValue = 0; + private textParts: string[] = []; + constructor() {} + + indent() { + this.indentValue += 1; + } + + unindent() { + this.indentValue -= 1; + } + + writeLine(line: string) { + for (let i = 0; i < this.indentValue; i+=1) { + this.textParts.push(this.indentText); + } + this.textParts.push(line); + this.textParts.push('\n'); + } + + getFullText() { + return this.textParts.join(''); + } +} + +// GENERATOR UTILITY FUNCTIONS + +function compareName(x: {name: string}, y: {name: string}): number { + if (x.name < y.name) { + return -1; + } else if (x.name > y.name) { + return 1 + } else { + return 0; + } +} + +function isNamespaceBase(obj: Protobuf.ReflectionObject): obj is Protobuf.NamespaceBase { + return Array.isArray((obj as Protobuf.NamespaceBase).nestedArray); +} + +function stripLeadingPeriod(name: string) { + return name.startsWith('.') ? name.substring(1) : name; +} + +function getImportPath(to: Protobuf.Type | Protobuf.Enum | Protobuf.Service): string { + /* If the thing we are importing is defined in a message, it is generated in + * the same file as that message. */ + if (to.parent instanceof Protobuf.Type) { + return getImportPath(to.parent); + } + return stripLeadingPeriod(to.fullName).replace(/\./g, '/'); +} + +function getPath(to: Protobuf.Type | Protobuf.Enum | Protobuf.Service) { + return stripLeadingPeriod(to.fullName).replace(/\./g, '/') + '.ts'; +} + +function getPathToRoot(from: Protobuf.NamespaceBase) { + const depth = stripLeadingPeriod(from.fullName).split('.').length - 1; + if (depth === 0) { + return './'; + } + let path = ''; + for (let i = 0; i < depth; i++) { + path += '../'; + } + return path; +} + +function getRelativeImportPath(from: Protobuf.Type | Protobuf.Service, to: Protobuf.Type | Protobuf.Enum | Protobuf.Service) { + return getPathToRoot(from) + getImportPath(to); +} + +function getTypeInterfaceName(type: Protobuf.Type | Protobuf.Enum | Protobuf.Service) { + return type.fullName.replace(/\./g, '_'); +} + +function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Service, from: Protobuf.Type | Protobuf.Service | undefined, options: GeneratorOptions) { + const filePath = from === undefined ? './' + getImportPath(dependency) : getRelativeImportPath(from, dependency); + const {outputName, inputName} = useNameFmter(options); + const typeInterfaceName = getTypeInterfaceName(dependency); + let importedTypes: string; + /* If the dependency is defined within a message, it will be generated in that + * message's file and exported using its typeInterfaceName. */ + if (dependency.parent instanceof Protobuf.Type) { + if (dependency instanceof Protobuf.Type || dependency instanceof Protobuf.Enum) { + importedTypes = `${inputName(typeInterfaceName)}, ${outputName(typeInterfaceName)}`; + } else if (dependency instanceof Protobuf.Service) { + importedTypes = `${typeInterfaceName}Client, ${typeInterfaceName}Definition`; + } else { + throw new Error('Invalid object passed to getImportLine'); + } + } else { + if (dependency instanceof Protobuf.Type || dependency instanceof Protobuf.Enum) { + importedTypes = `${inputName(dependency.name)} as ${inputName(typeInterfaceName)}, ${outputName(dependency.name)} as ${outputName(typeInterfaceName)}`; + } else if (dependency instanceof Protobuf.Service) { + importedTypes = `${dependency.name}Client as ${typeInterfaceName}Client, ${dependency.name}Definition as ${typeInterfaceName}Definition`; + } else { + throw new Error('Invalid object passed to getImportLine'); + } + } + return `import type { ${importedTypes} } from '${filePath}';` +} + +function getChildMessagesAndEnums(namespace: Protobuf.NamespaceBase): (Protobuf.Type | Protobuf.Enum)[] { + const messageList: (Protobuf.Type | Protobuf.Enum)[] = []; + for (const nested of namespace.nestedArray) { + if (nested instanceof Protobuf.Type || nested instanceof Protobuf.Enum) { + messageList.push(nested); + } + if (isNamespaceBase(nested)) { + messageList.push(...getChildMessagesAndEnums(nested)); + } + } + return messageList; +} + +function formatComment(formatter: TextFormatter, comment?: string | null) { + if (!comment) { + return; + } + formatter.writeLine('/**'); + for(const line of comment.split('\n')) { + formatter.writeLine(` * ${line.replace(/\*\//g, '* /')}`); + } + formatter.writeLine(' */'); +} + +const typeBrandHint = `This field is a type brand and is not populated at runtime. Instances of this type should be created using type assertions. +https://github.com/grpc/grpc-node/pull/2281`; + +function formatTypeBrand(formatter: TextFormatter, messageType: Protobuf.Type) { + formatComment(formatter, typeBrandHint); + formatter.writeLine(`__type: '${messageType.fullName}'`); +} + +// GENERATOR FUNCTIONS + +function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | Protobuf.Enum | null, repeated: boolean, map: boolean, options: GeneratorOptions): string { + const {inputName} = useNameFmter(options); + switch (fieldType) { + case 'double': + case 'float': + return 'number | string'; + case 'int32': + case 'uint32': + case 'sint32': + case 'fixed32': + case 'sfixed32': + return 'number'; + case 'int64': + case 'uint64': + case 'sint64': + case 'fixed64': + case 'sfixed64': + return 'number | string | Long'; + case 'bool': + return 'boolean'; + case 'string': + return 'string'; + case 'bytes': + return 'Buffer | Uint8Array | string'; + default: + if (resolvedType === null) { + throw new Error('Found field with no usable type'); + } + const typeInterfaceName = getTypeInterfaceName(resolvedType); + if (resolvedType instanceof Protobuf.Type) { + if (repeated || map) { + return inputName(typeInterfaceName); + } else { + return `${inputName(typeInterfaceName)} | null`; + } + } else { + // Enum + return inputName(typeInterfaceName); + } + } +} + +function getFieldTypePermissive(field: Protobuf.FieldBase, options: GeneratorOptions): string { + const valueType = getTypeNamePermissive(field.type, field.resolvedType, field.repeated, field.map, options); + if (field instanceof Protobuf.MapField) { + const keyType = field.keyType === 'string' ? 'string' : 'number'; + return `{[key: ${keyType}]: ${valueType}}`; + } else { + return valueType; + } +} + +function generatePermissiveMessageInterface(formatter: TextFormatter, messageType: Protobuf.Type, options: GeneratorOptions, nameOverride?: string) { + const {inputName} = useNameFmter(options); + if (options.includeComments) { + formatComment(formatter, messageType.comment); + } + if (messageType.fullName === '.google.protobuf.Any') { + /* This describes the behavior of the Protobuf.js Any wrapper fromObject + * replacement function */ + formatter.writeLine(`export type ${inputName('Any')} = AnyExtension | {`); + formatter.writeLine(' type_url: string;'); + formatter.writeLine(' value: Buffer | Uint8Array | string;'); + formatter.writeLine('}'); + return; + } + formatter.writeLine(`export interface ${inputName(nameOverride ?? messageType.name)} {`); + formatter.indent(); + for (const field of messageType.fieldsArray) { + const repeatedString = field.repeated ? '[]' : ''; + const type: string = getFieldTypePermissive(field, options); + if (options.includeComments) { + formatComment(formatter, field.comment); + } + formatter.writeLine(`'${field.name}'?: (${type})${repeatedString};`); + } + for (const oneof of messageType.oneofsArray) { + const typeString = oneof.fieldsArray.map(field => `"${field.name}"`).join('|'); + if (options.includeComments) { + formatComment(formatter, oneof.comment); + } + formatter.writeLine(`'${oneof.name}'?: ${typeString};`); + } + if (options.inputBranded) { + formatTypeBrand(formatter, messageType); + } + formatter.unindent(); + formatter.writeLine('}'); +} + +function getTypeNameRestricted(fieldType: string, resolvedType: Protobuf.Type | Protobuf.Enum | null, repeated: boolean, map: boolean, options: GeneratorOptions): string { + const {outputName} = useNameFmter(options); + switch (fieldType) { + case 'double': + case 'float': + if (options.json) { + return 'number | string'; + } else { + return 'number'; + } + case 'int32': + case 'uint32': + case 'sint32': + case 'fixed32': + case 'sfixed32': + return 'number'; + case 'int64': + case 'uint64': + case 'sint64': + case 'fixed64': + case 'sfixed64': + if (options.longs === Number) { + return 'number'; + } else if (options.longs === String) { + return 'string'; + } else { + return 'Long'; + } + case 'bool': + return 'boolean'; + case 'string': + return 'string'; + case 'bytes': + if (options.bytes === Array) { + return 'Uint8Array'; + } else if (options.bytes === String) { + return 'string'; + } else { + return 'Buffer'; + } + default: + if (resolvedType === null) { + throw new Error('Found field with no usable type'); + } + const typeInterfaceName = getTypeInterfaceName(resolvedType); + if (resolvedType instanceof Protobuf.Type) { + /* null is only used to represent absent message values if the defaults + * option is set, and only for non-repeated, non-map fields. */ + if (options.defaults && !repeated && !map) { + return `${outputName(typeInterfaceName)} | null`; + } else { + return `${outputName(typeInterfaceName)}`; + } + } else { + // Enum + return outputName(typeInterfaceName); + } + } +} + +function getFieldTypeRestricted(field: Protobuf.FieldBase, options: GeneratorOptions): string { + const valueType = getTypeNameRestricted(field.type, field.resolvedType, field.repeated, field.map, options); + if (field instanceof Protobuf.MapField) { + const keyType = field.keyType === 'string' ? 'string' : 'number'; + return `{[key: ${keyType}]: ${valueType}}`; + } else { + return valueType; + } +} + +function generateRestrictedMessageInterface(formatter: TextFormatter, messageType: Protobuf.Type, options: GeneratorOptions, nameOverride?: string) { + const {outputName} = useNameFmter(options); + if (options.includeComments) { + formatComment(formatter, messageType.comment); + } + if (messageType.fullName === '.google.protobuf.Any' && options.json) { + /* This describes the behavior of the Protobuf.js Any wrapper toObject + * replacement function */ + let optionalString = options.defaults ? '' : '?'; + formatter.writeLine(`export type ${outputName('Any')} = AnyExtension | {`); + formatter.writeLine(` type_url${optionalString}: string;`); + formatter.writeLine(` value${optionalString}: ${getTypeNameRestricted('bytes', null, false, false, options)};`); + formatter.writeLine('}'); + return; + } + formatter.writeLine(`export interface ${outputName(nameOverride ?? messageType.name)} {`); + formatter.indent(); + for (const field of messageType.fieldsArray) { + let fieldGuaranteed: boolean; + if (field.partOf) { + // The field is not guaranteed populated if it is part of a oneof + fieldGuaranteed = false; + } else if (field.repeated) { + fieldGuaranteed = (options.defaults || options.arrays) ?? false; + } else if (field.map) { + fieldGuaranteed = (options.defaults || options.objects) ?? false; + } else { + fieldGuaranteed = options.defaults ?? false; + } + const optionalString = fieldGuaranteed ? '' : '?'; + const repeatedString = field.repeated ? '[]' : ''; + const type = getFieldTypeRestricted(field, options); + if (options.includeComments) { + formatComment(formatter, field.comment); + } + formatter.writeLine(`'${field.name}'${optionalString}: (${type})${repeatedString};`); + } + if (options.oneofs) { + for (const oneof of messageType.oneofsArray) { + const typeString = oneof.fieldsArray.map(field => `"${field.name}"`).join('|'); + if (options.includeComments) { + formatComment(formatter, oneof.comment); + } + formatter.writeLine(`'${oneof.name}': ${typeString};`); + } + } + if (options.outputBranded) { + formatTypeBrand(formatter, messageType); + } + formatter.unindent(); + formatter.writeLine('}'); +} + +function generateMessageInterfaces(formatter: TextFormatter, messageType: Protobuf.Type, options: GeneratorOptions) { + let usesLong: boolean = false; + let seenDeps: Set = new Set(); + const childTypes = getChildMessagesAndEnums(messageType); + formatter.writeLine(`// Original file: ${(messageType.filename ?? 'null')?.replace(/\\/g, '/')}`); + formatter.writeLine(''); + messageType.fieldsArray.sort((fieldA, fieldB) => fieldA.id - fieldB.id); + for (const field of messageType.fieldsArray) { + if (field.resolvedType && childTypes.indexOf(field.resolvedType) < 0) { + const dependency = field.resolvedType; + if (seenDeps.has(dependency.fullName)) { + continue; + } + seenDeps.add(dependency.fullName); + formatter.writeLine(getImportLine(dependency, messageType, options)); + } + if (field.type.indexOf('64') >= 0) { + usesLong = true; + } + } + for (const childType of childTypes) { + if (childType instanceof Protobuf.Type) { + for (const field of childType.fieldsArray) { + if (field.resolvedType && childTypes.indexOf(field.resolvedType) < 0) { + const dependency = field.resolvedType; + if (seenDeps.has(dependency.fullName)) { + continue; + } + seenDeps.add(dependency.fullName); + formatter.writeLine(getImportLine(dependency, messageType, options)); + } + if (field.type.indexOf('64') >= 0) { + usesLong = true; + } + } + } + } + if (usesLong) { + formatter.writeLine("import type { Long } from '@grpc/proto-loader';"); + } + if (messageType.fullName === '.google.protobuf.Any') { + formatter.writeLine("import type { AnyExtension } from '@grpc/proto-loader';") + } + formatter.writeLine(''); + for (const childType of childTypes.sort(compareName)) { + const nameOverride = getTypeInterfaceName(childType); + if (childType instanceof Protobuf.Type) { + generatePermissiveMessageInterface(formatter, childType, options, nameOverride); + formatter.writeLine(''); + generateRestrictedMessageInterface(formatter, childType, options, nameOverride); + } else { + generateEnumInterface(formatter, childType, options, nameOverride); + } + formatter.writeLine(''); + } + + generatePermissiveMessageInterface(formatter, messageType, options); + formatter.writeLine(''); + generateRestrictedMessageInterface(formatter, messageType, options); +} + +function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum, options: GeneratorOptions, nameOverride?: string) { + const {inputName, outputName} = useNameFmter(options); + const name = nameOverride ?? enumType.name; + formatter.writeLine(`// Original file: ${(enumType.filename ?? 'null')?.replace(/\\/g, '/')}`); + formatter.writeLine(''); + if (options.includeComments) { + formatComment(formatter, enumType.comment); + } + formatter.writeLine(`export const ${name} = {`); + formatter.indent(); + for (const key of Object.keys(enumType.values)) { + if (options.includeComments) { + formatComment(formatter, enumType.comments[key]); + } + formatter.writeLine(`${key}: ${options.enums == String ? `'${key}'` : enumType.values[key]},`); + } + formatter.unindent(); + formatter.writeLine('} as const;'); + + // Permissive Type + formatter.writeLine(''); + if (options.includeComments) { + formatComment(formatter, enumType.comment); + } + formatter.writeLine(`export type ${inputName(name)} =`) + formatter.indent(); + for (const key of Object.keys(enumType.values)) { + if (options.includeComments) { + formatComment(formatter, enumType.comments[key]); + } + formatter.writeLine(`| '${key}'`); + formatter.writeLine(`| ${enumType.values[key]}`); + } + formatter.unindent(); + + // Restrictive Type + formatter.writeLine(''); + if (options.includeComments) { + formatComment(formatter, enumType.comment); + } + formatter.writeLine(`export type ${outputName(name)} = typeof ${name}[keyof typeof ${name}]`) +} + +/** + * This is a list of methods that are exist in the generic Client class in the + * gRPC libraries. TypeScript has a problem with methods in subclasses with the + * same names as methods in the superclass, but with mismatched APIs. So, we + * avoid generating methods with these names in the service client interfaces. + * We always generate two service client methods per service method: one camel + * cased, and one with the original casing. So we will still generate one + * service client method for any conflicting name. + * + * Technically, at runtime conflicting name in the service client method + * actually shadows the original method, but TypeScript does not have a good + * way to represent that. So this change is not 100% accurate, but it gets the + * generated code to compile. + * + * This is just a list of the methods in the Client class definitions in + * grpc@1.24.11 and @grpc/grpc-js@1.4.0. + */ +const CLIENT_RESERVED_METHOD_NAMES = new Set([ + 'close', + 'getChannel', + 'waitForReady', + 'makeUnaryRequest', + 'makeClientStreamRequest', + 'makeServerStreamRequest', + 'makeBidiStreamRequest', + 'resolveCallInterceptors', + /* These methods are private, but TypeScript is not happy with overriding even + * private methods with mismatched APIs. */ + 'checkOptionalUnaryResponseArguments', + 'checkMetadataAndOptions' +]); + +function generateServiceClientInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { + const {outputName, inputName} = useNameFmter(options); + if (options.includeComments) { + formatComment(formatter, serviceType.comment); + } + formatter.writeLine(`export interface ${serviceType.name}Client extends grpc.Client {`); + formatter.indent(); + for (const methodName of Object.keys(serviceType.methods).sort()) { + const method = serviceType.methods[methodName]; + for (const name of [methodName, camelCase(methodName)]) { + if (CLIENT_RESERVED_METHOD_NAMES.has(name)) { + continue; + } + if (options.includeComments) { + formatComment(formatter, method.comment); + } + const requestType = inputName(getTypeInterfaceName(method.resolvedRequestType!)); + const responseType = outputName(getTypeInterfaceName(method.resolvedResponseType!)); + const callbackType = `grpc.requestCallback<${responseType}>`; + if (method.requestStream) { + if (method.responseStream) { + // Bidi streaming + const callType = `grpc.ClientDuplexStream<${requestType}, ${responseType}>`; + formatter.writeLine(`${name}(metadata: grpc.Metadata, options?: grpc.CallOptions): ${callType};`); + formatter.writeLine(`${name}(options?: grpc.CallOptions): ${callType};`); + } else { + // Client streaming + const callType = `grpc.ClientWritableStream<${requestType}>`; + formatter.writeLine(`${name}(metadata: grpc.Metadata, options: grpc.CallOptions, callback: ${callbackType}): ${callType};`); + formatter.writeLine(`${name}(metadata: grpc.Metadata, callback: ${callbackType}): ${callType};`); + formatter.writeLine(`${name}(options: grpc.CallOptions, callback: ${callbackType}): ${callType};`); + formatter.writeLine(`${name}(callback: ${callbackType}): ${callType};`); + } + } else { + if (method.responseStream) { + // Server streaming + const callType = `grpc.ClientReadableStream<${responseType}>`; + formatter.writeLine(`${name}(argument: ${requestType}, metadata: grpc.Metadata, options?: grpc.CallOptions): ${callType};`); + formatter.writeLine(`${name}(argument: ${requestType}, options?: grpc.CallOptions): ${callType};`); + } else { + // Unary + const callType = 'grpc.ClientUnaryCall'; + formatter.writeLine(`${name}(argument: ${requestType}, metadata: grpc.Metadata, options: grpc.CallOptions, callback: ${callbackType}): ${callType};`); + formatter.writeLine(`${name}(argument: ${requestType}, metadata: grpc.Metadata, callback: ${callbackType}): ${callType};`); + formatter.writeLine(`${name}(argument: ${requestType}, options: grpc.CallOptions, callback: ${callbackType}): ${callType};`); + formatter.writeLine(`${name}(argument: ${requestType}, callback: ${callbackType}): ${callType};`); + } + } + } + formatter.writeLine(''); + } + formatter.unindent(); + formatter.writeLine('}'); +} + +function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { + const {inputName, outputName} = useNameFmter(options); + if (options.includeComments) { + formatComment(formatter, serviceType.comment); + } + formatter.writeLine(`export interface ${serviceType.name}Handlers extends grpc.UntypedServiceImplementation {`); + formatter.indent(); + for (const methodName of Object.keys(serviceType.methods).sort()) { + const method = serviceType.methods[methodName]; + if (options.includeComments) { + formatComment(formatter, method.comment); + } + const requestType = outputName(getTypeInterfaceName(method.resolvedRequestType!)); + const responseType = inputName(getTypeInterfaceName(method.resolvedResponseType!)); + if (method.requestStream) { + if (method.responseStream) { + // Bidi streaming + formatter.writeLine(`${methodName}: grpc.handleBidiStreamingCall<${requestType}, ${responseType}>;`); + } else { + // Client streaming + formatter.writeLine(`${methodName}: grpc.handleClientStreamingCall<${requestType}, ${responseType}>;`); + } + } else { + if (method.responseStream) { + // Server streaming + formatter.writeLine(`${methodName}: grpc.handleServerStreamingCall<${requestType}, ${responseType}>;`); + } else { + // Unary + formatter.writeLine(`${methodName}: grpc.handleUnaryCall<${requestType}, ${responseType}>;`); + } + } + formatter.writeLine(''); + } + formatter.unindent(); + formatter.writeLine('}'); +} + +function generateServiceDefinitionInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { + const {inputName, outputName} = useNameFmter(options); + formatter.writeLine(`export interface ${serviceType.name}Definition extends grpc.ServiceDefinition {`); + formatter.indent(); + for (const methodName of Object.keys(serviceType.methods).sort()) { + const method = serviceType.methods[methodName]; + const requestType = getTypeInterfaceName(method.resolvedRequestType!); + const responseType = getTypeInterfaceName(method.resolvedResponseType!); + formatter.writeLine(`${methodName}: MethodDefinition<${inputName(requestType)}, ${inputName(responseType)}, ${outputName(requestType)}, ${outputName(responseType)}>`); + } + formatter.unindent(); + formatter.writeLine('}') +} + +function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { + formatter.writeLine(`// Original file: ${(serviceType.filename ?? 'null')?.replace(/\\/g, '/')}`); + formatter.writeLine(''); + const grpcImportPath = options.grpcLib.startsWith('.') ? getPathToRoot(serviceType) + options.grpcLib : options.grpcLib; + formatter.writeLine(`import type * as grpc from '${grpcImportPath}'`); + formatter.writeLine(`import type { MethodDefinition } from '@grpc/proto-loader'`) + const dependencies: Set = new Set(); + for (const method of serviceType.methodsArray) { + dependencies.add(method.resolvedRequestType!); + dependencies.add(method.resolvedResponseType!); + } + for (const dep of Array.from(dependencies.values()).sort(compareName)) { + formatter.writeLine(getImportLine(dep, serviceType, options)); + } + formatter.writeLine(''); + + generateServiceClientInterface(formatter, serviceType, options); + formatter.writeLine(''); + + generateServiceHandlerInterface(formatter, serviceType, options); + formatter.writeLine(''); + + generateServiceDefinitionInterface(formatter, serviceType, options); +} + +function containsDefinition(definitionType: typeof Protobuf.Type | typeof Protobuf.Enum, namespace: Protobuf.NamespaceBase): boolean { + for (const nested of namespace.nestedArray.sort(compareName)) { + if (nested instanceof definitionType) { + return true; + } else if (isNamespaceBase(nested) && !(nested instanceof Protobuf.Type) && !(nested instanceof Protobuf.Enum) && containsDefinition(definitionType, nested)) { + return true; + } + } + + return false; +} + +function generateDefinitionImports(formatter: TextFormatter, namespace: Protobuf.NamespaceBase, options: GeneratorOptions) { + const imports = []; + + if (containsDefinition(Protobuf.Enum, namespace)) { + imports.push('EnumTypeDefinition'); + } + + if (containsDefinition(Protobuf.Type, namespace)) { + imports.push('MessageTypeDefinition'); + } + + if (imports.length) { + formatter.writeLine(`import type { ${imports.join(', ')} } from '@grpc/proto-loader';`); + } +} + +function generateServiceImports(formatter: TextFormatter, namespace: Protobuf.NamespaceBase, options: GeneratorOptions) { + for (const nested of namespace.nestedArray.sort(compareName)) { + if (nested instanceof Protobuf.Service) { + formatter.writeLine(getImportLine(nested, undefined, options)); + } else if (isNamespaceBase(nested) && !(nested instanceof Protobuf.Type) && !(nested instanceof Protobuf.Enum)) { + generateServiceImports(formatter, nested, options); + } + } +} + +function generateSingleLoadedDefinitionType(formatter: TextFormatter, nested: Protobuf.ReflectionObject, options: GeneratorOptions) { + if (nested instanceof Protobuf.Service) { + if (options.includeComments) { + formatComment(formatter, nested.comment); + } + const typeInterfaceName = getTypeInterfaceName(nested); + formatter.writeLine(`${nested.name}: SubtypeConstructor & { service: ${typeInterfaceName}Definition }`); + } else if (nested instanceof Protobuf.Enum) { + formatter.writeLine(`${nested.name}: EnumTypeDefinition`); + } else if (nested instanceof Protobuf.Type) { + formatter.writeLine(`${nested.name}: MessageTypeDefinition`); + } else if (isNamespaceBase(nested)) { + generateLoadedDefinitionTypes(formatter, nested, options); + } +} + +function generateLoadedDefinitionTypes(formatter: TextFormatter, namespace: Protobuf.NamespaceBase, options: GeneratorOptions) { + formatter.writeLine(`${namespace.name}: {`); + formatter.indent(); + for (const nested of namespace.nestedArray.sort(compareName)) { + generateSingleLoadedDefinitionType(formatter, nested, options); + } + formatter.unindent(); + formatter.writeLine('}'); +} + +function generateRootFile(formatter: TextFormatter, root: Protobuf.Root, options: GeneratorOptions) { + formatter.writeLine(`import type * as grpc from '${options.grpcLib}';`); + generateDefinitionImports(formatter, root, options); + formatter.writeLine(''); + + generateServiceImports(formatter, root, options); + formatter.writeLine(''); + + formatter.writeLine('type SubtypeConstructor any, Subtype> = {'); + formatter.writeLine(' new(...args: ConstructorParameters): Subtype;'); + formatter.writeLine('};'); + formatter.writeLine(''); + + formatter.writeLine('export interface ProtoGrpcType {'); + formatter.indent(); + for (const nested of root.nestedArray) { + generateSingleLoadedDefinitionType(formatter, nested, options); + } + formatter.unindent(); + formatter.writeLine('}'); + formatter.writeLine(''); +} + +async function writeFile(filename: string, contents: string): Promise { + await fs.promises.mkdir(path.dirname(filename), {recursive: true}); + return fs.promises.writeFile(filename, contents); +} + +function generateFilesForNamespace(namespace: Protobuf.NamespaceBase, options: GeneratorOptions): Promise[] { + const filePromises : Promise[] = []; + for (const nested of namespace.nestedArray) { + const fileFormatter = new TextFormatter(); + if (nested instanceof Protobuf.Type) { + generateMessageInterfaces(fileFormatter, nested, options); + if (options.verbose) { + console.log(`Writing ${options.outDir}/${getPath(nested)} from file ${nested.filename}`); + } + filePromises.push(writeFile(`${options.outDir}/${getPath(nested)}`, fileFormatter.getFullText())); + } else if (nested instanceof Protobuf.Enum) { + generateEnumInterface(fileFormatter, nested, options); + if (options.verbose) { + console.log(`Writing ${options.outDir}/${getPath(nested)} from file ${nested.filename}`); + } + filePromises.push(writeFile(`${options.outDir}/${getPath(nested)}`, fileFormatter.getFullText())); + } else if (nested instanceof Protobuf.Service) { + generateServiceInterfaces(fileFormatter, nested, options); + if (options.verbose) { + console.log(`Writing ${options.outDir}/${getPath(nested)} from file ${nested.filename}`); + } + filePromises.push(writeFile(`${options.outDir}/${getPath(nested)}`, fileFormatter.getFullText())); + } else if (isNamespaceBase(nested)) { + filePromises.push(...generateFilesForNamespace(nested, options)); + } + } + return filePromises; +} + +function writeFilesForRoot(root: Protobuf.Root, masterFileName: string, options: GeneratorOptions): Promise[] { + const filePromises: Promise[] = []; + + const masterFileFormatter = new TextFormatter(); + generateRootFile(masterFileFormatter, root, options); + if (options.verbose) { + console.log(`Writing ${options.outDir}/${masterFileName}`); + } + filePromises.push(writeFile(`${options.outDir}/${masterFileName}`, masterFileFormatter.getFullText())); + + filePromises.push(...generateFilesForNamespace(root, options)); + + return filePromises; +} + +async function writeAllFiles(protoFiles: string[], options: GeneratorOptions) { + await fs.promises.mkdir(options.outDir, {recursive: true}); + const basenameMap = new Map(); + for (const filename of protoFiles) { + const basename = path.basename(filename).replace(/\.proto$/, '.ts'); + if (basenameMap.has(basename)) { + basenameMap.get(basename)!.push(filename); + } else { + basenameMap.set(basename, [filename]); + } + } + for (const [basename, filenames] of basenameMap.entries()) { + const loadedRoot = await loadProtosWithOptions(filenames, options); + writeFilesForRoot(loadedRoot, basename, options); + } +} + +async function runScript() { + const boolDefaultFalseOption = { + boolean: true, + default: false, + }; + const argv = yargs + .parserConfiguration({ + 'parse-positional-numbers': false + }) + .option('keepCase', boolDefaultFalseOption) + .option('longs', { string: true, default: 'Long' }) + .option('enums', { string: true, default: 'number' }) + .option('bytes', { string: true, default: 'Buffer' }) + .option('defaults', boolDefaultFalseOption) + .option('arrays', boolDefaultFalseOption) + .option('objects', boolDefaultFalseOption) + .option('oneofs', boolDefaultFalseOption) + .option('json', boolDefaultFalseOption) + .boolean('verbose') + .option('includeComments', boolDefaultFalseOption) + .option('includeDirs', { + normalize: true, + array: true, + alias: 'I' + }) + .option('outDir', { + alias: 'O', + normalize: true, + }) + .option('grpcLib', { string: true }) + .option('inputTemplate', { string: true, default: `${templateStr}` }) + .option('outputTemplate', { string: true, default: `${templateStr}__Output` }) + .option('inputBranded', boolDefaultFalseOption) + .option('outputBranded', boolDefaultFalseOption) + .coerce('longs', value => { + switch (value) { + case 'String': return String; + case 'Number': return Number; + default: return undefined; + } + }).coerce('enums', value => { + if (value === 'String') { + return String; + } else { + return undefined; + } + }).coerce('bytes', value => { + switch (value) { + case 'Array': return Array; + case 'String': return String; + default: return undefined; + } + }) + .alias({ + verbose: 'v' + }).describe({ + keepCase: 'Preserve the case of field names', + longs: 'The type that should be used to output 64 bit integer values. Can be String, Number', + enums: 'The type that should be used to output enum fields. Can be String', + bytes: 'The type that should be used to output bytes fields. Can be String, Array', + defaults: 'Output default values for omitted fields', + arrays: 'Output default values for omitted repeated fields even if --defaults is not set', + objects: 'Output default values for omitted message fields even if --defaults is not set', + oneofs: 'Output virtual oneof fields set to the present field\'s name', + json: 'Represent Infinity and NaN as strings in float fields. Also decode google.protobuf.Any automatically', + includeComments: 'Generate doc comments from comments in the original files', + includeDirs: 'Directories to search for included files', + outDir: 'Directory in which to output files', + grpcLib: 'The gRPC implementation library that these types will be used with', + inputTemplate: 'Template for mapping input or "permissive" type names', + outputTemplate: 'Template for mapping output or "restricted" type names', + inputBranded: 'Output property for branded type for "permissive" types with fullName of the Message as its value', + outputBranded: 'Output property for branded type for "restricted" types with fullName of the Message as its value', + }).demandOption(['outDir', 'grpcLib']) + .demand(1) + .usage('$0 [options] filenames...') + .epilogue('WARNING: This tool is in alpha. The CLI and generated code are subject to change') + .argv; + if (argv.verbose) { + console.log('Parsed arguments:', argv); + } + addCommonProtos(); + writeAllFiles(argv._ as string[], {...argv, alternateCommentMode: true}).then(() => { + if (argv.verbose) { + console.log('Success'); + } + }, (error) => { + console.error(error) + process.exit(1); + }); +} + +if (require.main === module) { + runScript(); +} diff --git a/packages/proto-loader/deps/gapic-showcase b/packages/proto-loader/deps/gapic-showcase new file mode 160000 index 000000000..b09b3ba9a --- /dev/null +++ b/packages/proto-loader/deps/gapic-showcase @@ -0,0 +1 @@ +Subproject commit b09b3ba9a8db8aae7d5d7c3939853681cc97c293 diff --git a/packages/proto-loader/deps/googleapis b/packages/proto-loader/deps/googleapis new file mode 160000 index 000000000..8f2eda119 --- /dev/null +++ b/packages/proto-loader/deps/googleapis @@ -0,0 +1 @@ +Subproject commit 8f2eda119e11c8bd0c189b545da18bba9019c83e diff --git a/packages/proto-loader/golden-generated/echo.ts b/packages/proto-loader/golden-generated/echo.ts new file mode 100644 index 000000000..600e2864c --- /dev/null +++ b/packages/proto-loader/golden-generated/echo.ts @@ -0,0 +1,95 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { OperationsClient as _google_longrunning_OperationsClient, OperationsDefinition as _google_longrunning_OperationsDefinition } from './google/longrunning/Operations'; +import type { EchoClient as _google_showcase_v1beta1_EchoClient, EchoDefinition as _google_showcase_v1beta1_EchoDefinition } from './google/showcase/v1beta1/Echo'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + google: { + api: { + CustomHttpPattern: MessageTypeDefinition + FieldBehavior: EnumTypeDefinition + Http: MessageTypeDefinition + HttpRule: MessageTypeDefinition + } + longrunning: { + CancelOperationRequest: MessageTypeDefinition + DeleteOperationRequest: MessageTypeDefinition + GetOperationRequest: MessageTypeDefinition + ListOperationsRequest: MessageTypeDefinition + ListOperationsResponse: MessageTypeDefinition + Operation: MessageTypeDefinition + OperationInfo: MessageTypeDefinition + /** + * Manages long-running operations with an API service. + * + * When an API method normally takes long time to complete, it can be designed + * to return [Operation][google.longrunning.Operation] to the client, and the client can use this + * interface to receive the real response asynchronously by polling the + * operation resource, or pass the operation resource to another API (such as + * Google Cloud Pub/Sub API) to receive the response. Any API service that + * returns long-running operations should implement the `Operations` interface + * so developers can have a consistent client experience. + */ + Operations: SubtypeConstructor & { service: _google_longrunning_OperationsDefinition } + WaitOperationRequest: MessageTypeDefinition + } + protobuf: { + Any: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + Duration: MessageTypeDefinition + Empty: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + } + rpc: { + Status: MessageTypeDefinition + } + showcase: { + v1beta1: { + BlockRequest: MessageTypeDefinition + BlockResponse: MessageTypeDefinition + /** + * This service is used showcase the four main types of rpcs - unary, server + * side streaming, client side streaming, and bidirectional streaming. This + * service also exposes methods that explicitly implement server delay, and + * paginated calls. Set the 'showcase-trailer' metadata key on any method + * to have the values echoed in the response trailers. + */ + Echo: SubtypeConstructor & { service: _google_showcase_v1beta1_EchoDefinition } + EchoRequest: MessageTypeDefinition + EchoResponse: MessageTypeDefinition + ExpandRequest: MessageTypeDefinition + PagedExpandRequest: MessageTypeDefinition + PagedExpandResponse: MessageTypeDefinition + Severity: EnumTypeDefinition + WaitMetadata: MessageTypeDefinition + WaitRequest: MessageTypeDefinition + WaitResponse: MessageTypeDefinition + } + } + } +} + diff --git a/packages/proto-loader/golden-generated/google/api/CustomHttpPattern.ts b/packages/proto-loader/golden-generated/google/api/CustomHttpPattern.ts new file mode 100644 index 000000000..2f6e20297 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/api/CustomHttpPattern.ts @@ -0,0 +1,30 @@ +// Original file: deps/googleapis/google/api/http.proto + + +/** + * A custom pattern is used for defining custom HTTP verb. + */ +export interface ICustomHttpPattern { + /** + * The name of this custom HTTP verb. + */ + 'kind'?: (string); + /** + * The path matched by this custom verb. + */ + 'path'?: (string); +} + +/** + * A custom pattern is used for defining custom HTTP verb. + */ +export interface OCustomHttpPattern { + /** + * The name of this custom HTTP verb. + */ + 'kind': (string); + /** + * The path matched by this custom verb. + */ + 'path': (string); +} diff --git a/packages/proto-loader/golden-generated/google/api/FieldBehavior.ts b/packages/proto-loader/golden-generated/google/api/FieldBehavior.ts new file mode 100644 index 000000000..189d25be5 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/api/FieldBehavior.ts @@ -0,0 +1,108 @@ +// Original file: deps/googleapis/google/api/field_behavior.proto + +/** + * An indicator of the behavior of a given field (for example, that a field + * is required in requests, or given as output but ignored as input). + * This **does not** change the behavior in protocol buffers itself; it only + * denotes the behavior and may affect how API tooling handles the field. + * + * Note: This enum **may** receive new values in the future. + */ +export const FieldBehavior = { + /** + * Conventional default for enums. Do not use this. + */ + FIELD_BEHAVIOR_UNSPECIFIED: 'FIELD_BEHAVIOR_UNSPECIFIED', + /** + * Specifically denotes a field as optional. + * While all fields in protocol buffers are optional, this may be specified + * for emphasis if appropriate. + */ + OPTIONAL: 'OPTIONAL', + /** + * Denotes a field as required. + * This indicates that the field **must** be provided as part of the request, + * and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + */ + REQUIRED: 'REQUIRED', + /** + * Denotes a field as output only. + * This indicates that the field is provided in responses, but including the + * field in a request does nothing (the server *must* ignore it and + * *must not* throw an error as a result of the field's presence). + */ + OUTPUT_ONLY: 'OUTPUT_ONLY', + /** + * Denotes a field as input only. + * This indicates that the field is provided in requests, and the + * corresponding field is not included in output. + */ + INPUT_ONLY: 'INPUT_ONLY', + /** + * Denotes a field as immutable. + * This indicates that the field may be set once in a request to create a + * resource, but may not be changed thereafter. + */ + IMMUTABLE: 'IMMUTABLE', +} as const; + +/** + * An indicator of the behavior of a given field (for example, that a field + * is required in requests, or given as output but ignored as input). + * This **does not** change the behavior in protocol buffers itself; it only + * denotes the behavior and may affect how API tooling handles the field. + * + * Note: This enum **may** receive new values in the future. + */ +export type IFieldBehavior = + /** + * Conventional default for enums. Do not use this. + */ + | 'FIELD_BEHAVIOR_UNSPECIFIED' + | 0 + /** + * Specifically denotes a field as optional. + * While all fields in protocol buffers are optional, this may be specified + * for emphasis if appropriate. + */ + | 'OPTIONAL' + | 1 + /** + * Denotes a field as required. + * This indicates that the field **must** be provided as part of the request, + * and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + */ + | 'REQUIRED' + | 2 + /** + * Denotes a field as output only. + * This indicates that the field is provided in responses, but including the + * field in a request does nothing (the server *must* ignore it and + * *must not* throw an error as a result of the field's presence). + */ + | 'OUTPUT_ONLY' + | 3 + /** + * Denotes a field as input only. + * This indicates that the field is provided in requests, and the + * corresponding field is not included in output. + */ + | 'INPUT_ONLY' + | 4 + /** + * Denotes a field as immutable. + * This indicates that the field may be set once in a request to create a + * resource, but may not be changed thereafter. + */ + | 'IMMUTABLE' + | 5 + +/** + * An indicator of the behavior of a given field (for example, that a field + * is required in requests, or given as output but ignored as input). + * This **does not** change the behavior in protocol buffers itself; it only + * denotes the behavior and may affect how API tooling handles the field. + * + * Note: This enum **may** receive new values in the future. + */ +export type OFieldBehavior = typeof FieldBehavior[keyof typeof FieldBehavior] diff --git a/packages/proto-loader/golden-generated/google/api/Http.ts b/packages/proto-loader/golden-generated/google/api/Http.ts new file mode 100644 index 000000000..6b6ae8a63 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/api/Http.ts @@ -0,0 +1,49 @@ +// Original file: deps/googleapis/google/api/http.proto + +import type { IHttpRule as I_google_api_HttpRule, OHttpRule as O_google_api_HttpRule } from '../../google/api/HttpRule'; + +/** + * Defines the HTTP configuration for an API service. It contains a list of + * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method + * to one or more HTTP REST API methods. + */ +export interface IHttp { + /** + * A list of HTTP configuration rules that apply to individual API methods. + * + * **NOTE:** All service configuration rules follow "last one wins" order. + */ + 'rules'?: (I_google_api_HttpRule)[]; + /** + * When set to true, URL path parameters will be fully URI-decoded except in + * cases of single segment matches in reserved expansion, where "%2F" will be + * left encoded. + * + * The default behavior is to not decode RFC 6570 reserved characters in multi + * segment matches. + */ + 'fully_decode_reserved_expansion'?: (boolean); +} + +/** + * Defines the HTTP configuration for an API service. It contains a list of + * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method + * to one or more HTTP REST API methods. + */ +export interface OHttp { + /** + * A list of HTTP configuration rules that apply to individual API methods. + * + * **NOTE:** All service configuration rules follow "last one wins" order. + */ + 'rules': (O_google_api_HttpRule)[]; + /** + * When set to true, URL path parameters will be fully URI-decoded except in + * cases of single segment matches in reserved expansion, where "%2F" will be + * left encoded. + * + * The default behavior is to not decode RFC 6570 reserved characters in multi + * segment matches. + */ + 'fully_decode_reserved_expansion': (boolean); +} diff --git a/packages/proto-loader/golden-generated/google/api/HttpRule.ts b/packages/proto-loader/golden-generated/google/api/HttpRule.ts new file mode 100644 index 000000000..90efdc00d --- /dev/null +++ b/packages/proto-loader/golden-generated/google/api/HttpRule.ts @@ -0,0 +1,680 @@ +// Original file: deps/googleapis/google/api/http.proto + +import type { ICustomHttpPattern as I_google_api_CustomHttpPattern, OCustomHttpPattern as O_google_api_CustomHttpPattern } from '../../google/api/CustomHttpPattern'; +import type { IHttpRule as I_google_api_HttpRule, OHttpRule as O_google_api_HttpRule } from '../../google/api/HttpRule'; + +/** + * # gRPC Transcoding + * + * gRPC Transcoding is a feature for mapping between a gRPC method and one or + * more HTTP REST endpoints. It allows developers to build a single API service + * that supports both gRPC APIs and REST APIs. Many systems, including [Google + * APIs](https://github.com/googleapis/googleapis), + * [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC + * Gateway](https://github.com/grpc-ecosystem/grpc-gateway), + * and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature + * and use it for large scale production services. + * + * `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies + * how different portions of the gRPC request message are mapped to the URL + * path, URL query parameters, and HTTP request body. It also controls how the + * gRPC response message is mapped to the HTTP response body. `HttpRule` is + * typically specified as an `google.api.http` annotation on the gRPC method. + * + * Each mapping specifies a URL path template and an HTTP method. The path + * template may refer to one or more fields in the gRPC request message, as long + * as each field is a non-repeated field with a primitive (non-message) type. + * The path template controls how fields of the request message are mapped to + * the URL path. + * + * Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/{name=messages/*}" + * }; + * } + * } + * message GetMessageRequest { + * string name = 1; // Mapped to URL path. + * } + * message Message { + * string text = 1; // The resource content. + * } + * + * This enables an HTTP REST to gRPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` + * + * Any fields in the request message which are not bound by the path template + * automatically become HTTP query parameters if there is no HTTP request body. + * For example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get:"/v1/messages/{message_id}" + * }; + * } + * } + * message GetMessageRequest { + * message SubMessage { + * string subfield = 1; + * } + * string message_id = 1; // Mapped to URL path. + * int64 revision = 2; // Mapped to URL query parameter `revision`. + * SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. + * } + * + * This enables a HTTP JSON to RPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456?revision=2&sub.subfield=foo` | + * `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: + * "foo"))` + * + * Note that fields which are mapped to URL query parameters must have a + * primitive type or a repeated primitive type or a non-repeated message type. + * In the case of a repeated type, the parameter can be repeated in the URL + * as `...?param=A¶m=B`. In the case of a message type, each field of the + * message is mapped to a separate parameter, such as + * `...?foo.a=A&foo.b=B&foo.c=C`. + * + * For HTTP methods that allow a request body, the `body` field + * specifies the mapping. Consider a REST update method on the + * message resource collection: + * + * service Messaging { + * rpc UpdateMessage(UpdateMessageRequest) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "message" + * }; + * } + * } + * message UpdateMessageRequest { + * string message_id = 1; // mapped to the URL + * Message message = 2; // mapped to the body + * } + * + * The following HTTP JSON to RPC mapping is enabled, where the + * representation of the JSON in the request body is determined by + * protos JSON encoding: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" message { text: "Hi!" })` + * + * The special name `*` can be used in the body mapping to define that + * every field not bound by the path template should be mapped to the + * request body. This enables the following alternative definition of + * the update method: + * + * service Messaging { + * rpc UpdateMessage(Message) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "*" + * }; + * } + * } + * message Message { + * string message_id = 1; + * string text = 2; + * } + * + * + * The following HTTP JSON to RPC mapping is enabled: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" text: "Hi!")` + * + * Note that when using `*` in the body mapping, it is not possible to + * have HTTP parameters, as all fields not bound by the path end in + * the body. This makes this option more rarely used in practice when + * defining REST APIs. The common usage of `*` is in custom methods + * which don't use the URL at all for transferring data. + * + * It is possible to define multiple HTTP methods for one RPC by using + * the `additional_bindings` option. Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/messages/{message_id}" + * additional_bindings { + * get: "/v1/users/{user_id}/messages/{message_id}" + * } + * }; + * } + * } + * message GetMessageRequest { + * string message_id = 1; + * string user_id = 2; + * } + * + * This enables the following two alternative HTTP JSON to RPC mappings: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` + * `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: + * "123456")` + * + * ## Rules for HTTP mapping + * + * 1. Leaf request fields (recursive expansion nested messages in the request + * message) are classified into three categories: + * - Fields referred by the path template. They are passed via the URL path. + * - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP + * request body. + * - All other fields are passed via the URL query parameters, and the + * parameter name is the field path in the request message. A repeated + * field can be represented as multiple query parameters under the same + * name. + * 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields + * are passed via URL path and HTTP request body. + * 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all + * fields are passed via URL path and URL query parameters. + * + * ### Path template syntax + * + * Template = "/" Segments [ Verb ] ; + * Segments = Segment { "/" Segment } ; + * Segment = "*" | "**" | LITERAL | Variable ; + * Variable = "{" FieldPath [ "=" Segments ] "}" ; + * FieldPath = IDENT { "." IDENT } ; + * Verb = ":" LITERAL ; + * + * The syntax `*` matches a single URL path segment. The syntax `**` matches + * zero or more URL path segments, which must be the last part of the URL path + * except the `Verb`. + * + * The syntax `Variable` matches part of the URL path as specified by its + * template. A variable template must not contain other variables. If a variable + * matches a single path segment, its template may be omitted, e.g. `{var}` + * is equivalent to `{var=*}`. + * + * The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` + * contains any reserved character, such characters should be percent-encoded + * before the matching. + * + * If a variable contains exactly one path segment, such as `"{var}"` or + * `"{var=*}"`, when such a variable is expanded into a URL path on the client + * side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The + * server side does the reverse decoding. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{var}`. + * + * If a variable contains multiple path segments, such as `"{var=foo/*}"` + * or `"{var=**}"`, when such a variable is expanded into a URL path on the + * client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. + * The server side does the reverse decoding, except "%2F" and "%2f" are left + * unchanged. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{+var}`. + * + * ## Using gRPC API Service Configuration + * + * gRPC API Service Configuration (service config) is a configuration language + * for configuring a gRPC service to become a user-facing product. The + * service config is simply the YAML representation of the `google.api.Service` + * proto message. + * + * As an alternative to annotating your proto file, you can configure gRPC + * transcoding in your service config YAML files. You do this by specifying a + * `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same + * effect as the proto annotation. This can be particularly useful if you + * have a proto that is reused in multiple services. Note that any transcoding + * specified in the service config will override any matching transcoding + * configuration in the proto. + * + * Example: + * + * http: + * rules: + * # Selects a gRPC method and applies HttpRule to it. + * - selector: example.v1.Messaging.GetMessage + * get: /v1/messages/{message_id}/{sub.subfield} + * + * ## Special notes + * + * When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the + * proto to JSON conversion must follow the [proto3 + * specification](https://developers.google.com/protocol-buffers/docs/proto3#json). + * + * While the single segment variable follows the semantics of + * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String + * Expansion, the multi segment variable **does not** follow RFC 6570 Section + * 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion + * does not expand special characters like `?` and `#`, which would lead + * to invalid URLs. As the result, gRPC Transcoding uses a custom encoding + * for multi segment variables. + * + * The path variables **must not** refer to any repeated or mapped field, + * because client libraries are not capable of handling such variable expansion. + * + * The path variables **must not** capture the leading "/" character. The reason + * is that the most common use case "{var}" does not capture the leading "/" + * character. For consistency, all path variables must share the same behavior. + * + * Repeated message fields must not be mapped to URL query parameters, because + * no client library can support such complicated mapping. + * + * If an API needs to use a JSON array for request or response body, it can map + * the request or response body to a repeated field. However, some gRPC + * Transcoding implementations may not support this feature. + */ +export interface IHttpRule { + /** + * Selects a method to which this rule applies. + * + * Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + */ + 'selector'?: (string); + /** + * Maps to HTTP GET. Used for listing and getting information about + * resources. + */ + 'get'?: (string); + /** + * Maps to HTTP PUT. Used for replacing a resource. + */ + 'put'?: (string); + /** + * Maps to HTTP POST. Used for creating a resource or performing an action. + */ + 'post'?: (string); + /** + * Maps to HTTP DELETE. Used for deleting a resource. + */ + 'delete'?: (string); + /** + * Maps to HTTP PATCH. Used for updating a resource. + */ + 'patch'?: (string); + /** + * The name of the request field whose value is mapped to the HTTP request + * body, or `*` for mapping all request fields not captured by the path + * pattern to the HTTP body, or omitted for not having any HTTP request body. + * + * NOTE: the referred field must be present at the top-level of the request + * message type. + */ + 'body'?: (string); + /** + * The custom pattern is used for specifying an HTTP method that is not + * included in the `pattern` field, such as HEAD, or "*" to leave the + * HTTP method unspecified for this rule. The wild-card rule is useful + * for services that provide content to Web (HTML) clients. + */ + 'custom'?: (I_google_api_CustomHttpPattern | null); + /** + * Additional HTTP bindings for the selector. Nested bindings must + * not contain an `additional_bindings` field themselves (that is, + * the nesting may only be one level deep). + */ + 'additional_bindings'?: (I_google_api_HttpRule)[]; + /** + * Optional. The name of the response field whose value is mapped to the HTTP + * response body. When omitted, the entire response message will be used + * as the HTTP response body. + * + * NOTE: The referred field must be present at the top-level of the response + * message type. + */ + 'response_body'?: (string); + /** + * Determines the URL pattern is matched by this rules. This pattern can be + * used with any of the {get|put|post|delete|patch} methods. A custom method + * can be defined using the 'custom' field. + */ + 'pattern'?: "get"|"put"|"post"|"delete"|"patch"|"custom"; +} + +/** + * # gRPC Transcoding + * + * gRPC Transcoding is a feature for mapping between a gRPC method and one or + * more HTTP REST endpoints. It allows developers to build a single API service + * that supports both gRPC APIs and REST APIs. Many systems, including [Google + * APIs](https://github.com/googleapis/googleapis), + * [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC + * Gateway](https://github.com/grpc-ecosystem/grpc-gateway), + * and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature + * and use it for large scale production services. + * + * `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies + * how different portions of the gRPC request message are mapped to the URL + * path, URL query parameters, and HTTP request body. It also controls how the + * gRPC response message is mapped to the HTTP response body. `HttpRule` is + * typically specified as an `google.api.http` annotation on the gRPC method. + * + * Each mapping specifies a URL path template and an HTTP method. The path + * template may refer to one or more fields in the gRPC request message, as long + * as each field is a non-repeated field with a primitive (non-message) type. + * The path template controls how fields of the request message are mapped to + * the URL path. + * + * Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/{name=messages/*}" + * }; + * } + * } + * message GetMessageRequest { + * string name = 1; // Mapped to URL path. + * } + * message Message { + * string text = 1; // The resource content. + * } + * + * This enables an HTTP REST to gRPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` + * + * Any fields in the request message which are not bound by the path template + * automatically become HTTP query parameters if there is no HTTP request body. + * For example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get:"/v1/messages/{message_id}" + * }; + * } + * } + * message GetMessageRequest { + * message SubMessage { + * string subfield = 1; + * } + * string message_id = 1; // Mapped to URL path. + * int64 revision = 2; // Mapped to URL query parameter `revision`. + * SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. + * } + * + * This enables a HTTP JSON to RPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456?revision=2&sub.subfield=foo` | + * `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: + * "foo"))` + * + * Note that fields which are mapped to URL query parameters must have a + * primitive type or a repeated primitive type or a non-repeated message type. + * In the case of a repeated type, the parameter can be repeated in the URL + * as `...?param=A¶m=B`. In the case of a message type, each field of the + * message is mapped to a separate parameter, such as + * `...?foo.a=A&foo.b=B&foo.c=C`. + * + * For HTTP methods that allow a request body, the `body` field + * specifies the mapping. Consider a REST update method on the + * message resource collection: + * + * service Messaging { + * rpc UpdateMessage(UpdateMessageRequest) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "message" + * }; + * } + * } + * message UpdateMessageRequest { + * string message_id = 1; // mapped to the URL + * Message message = 2; // mapped to the body + * } + * + * The following HTTP JSON to RPC mapping is enabled, where the + * representation of the JSON in the request body is determined by + * protos JSON encoding: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" message { text: "Hi!" })` + * + * The special name `*` can be used in the body mapping to define that + * every field not bound by the path template should be mapped to the + * request body. This enables the following alternative definition of + * the update method: + * + * service Messaging { + * rpc UpdateMessage(Message) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "*" + * }; + * } + * } + * message Message { + * string message_id = 1; + * string text = 2; + * } + * + * + * The following HTTP JSON to RPC mapping is enabled: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" text: "Hi!")` + * + * Note that when using `*` in the body mapping, it is not possible to + * have HTTP parameters, as all fields not bound by the path end in + * the body. This makes this option more rarely used in practice when + * defining REST APIs. The common usage of `*` is in custom methods + * which don't use the URL at all for transferring data. + * + * It is possible to define multiple HTTP methods for one RPC by using + * the `additional_bindings` option. Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/messages/{message_id}" + * additional_bindings { + * get: "/v1/users/{user_id}/messages/{message_id}" + * } + * }; + * } + * } + * message GetMessageRequest { + * string message_id = 1; + * string user_id = 2; + * } + * + * This enables the following two alternative HTTP JSON to RPC mappings: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` + * `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: + * "123456")` + * + * ## Rules for HTTP mapping + * + * 1. Leaf request fields (recursive expansion nested messages in the request + * message) are classified into three categories: + * - Fields referred by the path template. They are passed via the URL path. + * - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP + * request body. + * - All other fields are passed via the URL query parameters, and the + * parameter name is the field path in the request message. A repeated + * field can be represented as multiple query parameters under the same + * name. + * 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields + * are passed via URL path and HTTP request body. + * 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all + * fields are passed via URL path and URL query parameters. + * + * ### Path template syntax + * + * Template = "/" Segments [ Verb ] ; + * Segments = Segment { "/" Segment } ; + * Segment = "*" | "**" | LITERAL | Variable ; + * Variable = "{" FieldPath [ "=" Segments ] "}" ; + * FieldPath = IDENT { "." IDENT } ; + * Verb = ":" LITERAL ; + * + * The syntax `*` matches a single URL path segment. The syntax `**` matches + * zero or more URL path segments, which must be the last part of the URL path + * except the `Verb`. + * + * The syntax `Variable` matches part of the URL path as specified by its + * template. A variable template must not contain other variables. If a variable + * matches a single path segment, its template may be omitted, e.g. `{var}` + * is equivalent to `{var=*}`. + * + * The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` + * contains any reserved character, such characters should be percent-encoded + * before the matching. + * + * If a variable contains exactly one path segment, such as `"{var}"` or + * `"{var=*}"`, when such a variable is expanded into a URL path on the client + * side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The + * server side does the reverse decoding. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{var}`. + * + * If a variable contains multiple path segments, such as `"{var=foo/*}"` + * or `"{var=**}"`, when such a variable is expanded into a URL path on the + * client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. + * The server side does the reverse decoding, except "%2F" and "%2f" are left + * unchanged. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{+var}`. + * + * ## Using gRPC API Service Configuration + * + * gRPC API Service Configuration (service config) is a configuration language + * for configuring a gRPC service to become a user-facing product. The + * service config is simply the YAML representation of the `google.api.Service` + * proto message. + * + * As an alternative to annotating your proto file, you can configure gRPC + * transcoding in your service config YAML files. You do this by specifying a + * `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same + * effect as the proto annotation. This can be particularly useful if you + * have a proto that is reused in multiple services. Note that any transcoding + * specified in the service config will override any matching transcoding + * configuration in the proto. + * + * Example: + * + * http: + * rules: + * # Selects a gRPC method and applies HttpRule to it. + * - selector: example.v1.Messaging.GetMessage + * get: /v1/messages/{message_id}/{sub.subfield} + * + * ## Special notes + * + * When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the + * proto to JSON conversion must follow the [proto3 + * specification](https://developers.google.com/protocol-buffers/docs/proto3#json). + * + * While the single segment variable follows the semantics of + * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String + * Expansion, the multi segment variable **does not** follow RFC 6570 Section + * 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion + * does not expand special characters like `?` and `#`, which would lead + * to invalid URLs. As the result, gRPC Transcoding uses a custom encoding + * for multi segment variables. + * + * The path variables **must not** refer to any repeated or mapped field, + * because client libraries are not capable of handling such variable expansion. + * + * The path variables **must not** capture the leading "/" character. The reason + * is that the most common use case "{var}" does not capture the leading "/" + * character. For consistency, all path variables must share the same behavior. + * + * Repeated message fields must not be mapped to URL query parameters, because + * no client library can support such complicated mapping. + * + * If an API needs to use a JSON array for request or response body, it can map + * the request or response body to a repeated field. However, some gRPC + * Transcoding implementations may not support this feature. + */ +export interface OHttpRule { + /** + * Selects a method to which this rule applies. + * + * Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + */ + 'selector': (string); + /** + * Maps to HTTP GET. Used for listing and getting information about + * resources. + */ + 'get'?: (string); + /** + * Maps to HTTP PUT. Used for replacing a resource. + */ + 'put'?: (string); + /** + * Maps to HTTP POST. Used for creating a resource or performing an action. + */ + 'post'?: (string); + /** + * Maps to HTTP DELETE. Used for deleting a resource. + */ + 'delete'?: (string); + /** + * Maps to HTTP PATCH. Used for updating a resource. + */ + 'patch'?: (string); + /** + * The name of the request field whose value is mapped to the HTTP request + * body, or `*` for mapping all request fields not captured by the path + * pattern to the HTTP body, or omitted for not having any HTTP request body. + * + * NOTE: the referred field must be present at the top-level of the request + * message type. + */ + 'body': (string); + /** + * The custom pattern is used for specifying an HTTP method that is not + * included in the `pattern` field, such as HEAD, or "*" to leave the + * HTTP method unspecified for this rule. The wild-card rule is useful + * for services that provide content to Web (HTML) clients. + */ + 'custom'?: (O_google_api_CustomHttpPattern | null); + /** + * Additional HTTP bindings for the selector. Nested bindings must + * not contain an `additional_bindings` field themselves (that is, + * the nesting may only be one level deep). + */ + 'additional_bindings': (O_google_api_HttpRule)[]; + /** + * Optional. The name of the response field whose value is mapped to the HTTP + * response body. When omitted, the entire response message will be used + * as the HTTP response body. + * + * NOTE: The referred field must be present at the top-level of the response + * message type. + */ + 'response_body': (string); + /** + * Determines the URL pattern is matched by this rules. This pattern can be + * used with any of the {get|put|post|delete|patch} methods. A custom method + * can be defined using the 'custom' field. + */ + 'pattern': "get"|"put"|"post"|"delete"|"patch"|"custom"; +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/CancelOperationRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/CancelOperationRequest.ts new file mode 100644 index 000000000..7e0f15ed8 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/CancelOperationRequest.ts @@ -0,0 +1,22 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + + +/** + * The request message for [Operations.CancelOperation][google.longrunning.Operations.CancelOperation]. + */ +export interface ICancelOperationRequest { + /** + * The name of the operation resource to be cancelled. + */ + 'name'?: (string); +} + +/** + * The request message for [Operations.CancelOperation][google.longrunning.Operations.CancelOperation]. + */ +export interface OCancelOperationRequest { + /** + * The name of the operation resource to be cancelled. + */ + 'name': (string); +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/DeleteOperationRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/DeleteOperationRequest.ts new file mode 100644 index 000000000..39d669d0a --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/DeleteOperationRequest.ts @@ -0,0 +1,22 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + + +/** + * The request message for [Operations.DeleteOperation][google.longrunning.Operations.DeleteOperation]. + */ +export interface IDeleteOperationRequest { + /** + * The name of the operation resource to be deleted. + */ + 'name'?: (string); +} + +/** + * The request message for [Operations.DeleteOperation][google.longrunning.Operations.DeleteOperation]. + */ +export interface ODeleteOperationRequest { + /** + * The name of the operation resource to be deleted. + */ + 'name': (string); +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/GetOperationRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/GetOperationRequest.ts new file mode 100644 index 000000000..9667e2e87 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/GetOperationRequest.ts @@ -0,0 +1,22 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + + +/** + * The request message for [Operations.GetOperation][google.longrunning.Operations.GetOperation]. + */ +export interface IGetOperationRequest { + /** + * The name of the operation resource. + */ + 'name'?: (string); +} + +/** + * The request message for [Operations.GetOperation][google.longrunning.Operations.GetOperation]. + */ +export interface OGetOperationRequest { + /** + * The name of the operation resource. + */ + 'name': (string); +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/ListOperationsRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/ListOperationsRequest.ts new file mode 100644 index 000000000..49dcd39f0 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/ListOperationsRequest.ts @@ -0,0 +1,46 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + + +/** + * The request message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. + */ +export interface IListOperationsRequest { + /** + * The standard list filter. + */ + 'filter'?: (string); + /** + * The standard list page size. + */ + 'page_size'?: (number); + /** + * The standard list page token. + */ + 'page_token'?: (string); + /** + * The name of the operation's parent resource. + */ + 'name'?: (string); +} + +/** + * The request message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. + */ +export interface OListOperationsRequest { + /** + * The standard list filter. + */ + 'filter': (string); + /** + * The standard list page size. + */ + 'page_size': (number); + /** + * The standard list page token. + */ + 'page_token': (string); + /** + * The name of the operation's parent resource. + */ + 'name': (string); +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/ListOperationsResponse.ts b/packages/proto-loader/golden-generated/google/longrunning/ListOperationsResponse.ts new file mode 100644 index 000000000..1e8b9ed5a --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/ListOperationsResponse.ts @@ -0,0 +1,31 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + +import type { IOperation as I_google_longrunning_Operation, OOperation as O_google_longrunning_Operation } from '../../google/longrunning/Operation'; + +/** + * The response message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. + */ +export interface IListOperationsResponse { + /** + * A list of operations that matches the specified filter in the request. + */ + 'operations'?: (I_google_longrunning_Operation)[]; + /** + * The standard List next-page token. + */ + 'next_page_token'?: (string); +} + +/** + * The response message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. + */ +export interface OListOperationsResponse { + /** + * A list of operations that matches the specified filter in the request. + */ + 'operations': (O_google_longrunning_Operation)[]; + /** + * The standard List next-page token. + */ + 'next_page_token': (string); +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/Operation.ts b/packages/proto-loader/golden-generated/google/longrunning/Operation.ts new file mode 100644 index 000000000..bbd1d8078 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/Operation.ts @@ -0,0 +1,98 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + +import type { IAny as I_google_protobuf_Any, OAny as O_google_protobuf_Any } from '../../google/protobuf/Any'; +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../google/rpc/Status'; + +/** + * This resource represents a long-running operation that is the result of a + * network API call. + */ +export interface IOperation { + /** + * The server-assigned name, which is only unique within the same service that + * originally returns it. If you use the default HTTP mapping, the + * `name` should be a resource name ending with `operations/{unique_id}`. + */ + 'name'?: (string); + /** + * Service-specific metadata associated with the operation. It typically + * contains progress information and common metadata such as create time. + * Some services might not provide such metadata. Any method that returns a + * long-running operation should document the metadata type, if any. + */ + 'metadata'?: (I_google_protobuf_Any | null); + /** + * If the value is `false`, it means the operation is still in progress. + * If `true`, the operation is completed, and either `error` or `response` is + * available. + */ + 'done'?: (boolean); + /** + * The error result of the operation in case of failure or cancellation. + */ + 'error'?: (I_google_rpc_Status | null); + /** + * The normal response of the operation in case of success. If the original + * method returns no data on success, such as `Delete`, the response is + * `google.protobuf.Empty`. If the original method is standard + * `Get`/`Create`/`Update`, the response should be the resource. For other + * methods, the response should have the type `XxxResponse`, where `Xxx` + * is the original method name. For example, if the original method name + * is `TakeSnapshot()`, the inferred response type is + * `TakeSnapshotResponse`. + */ + 'response'?: (I_google_protobuf_Any | null); + /** + * The operation result, which can be either an `error` or a valid `response`. + * If `done` == `false`, neither `error` nor `response` is set. + * If `done` == `true`, exactly one of `error` or `response` is set. + */ + 'result'?: "error"|"response"; +} + +/** + * This resource represents a long-running operation that is the result of a + * network API call. + */ +export interface OOperation { + /** + * The server-assigned name, which is only unique within the same service that + * originally returns it. If you use the default HTTP mapping, the + * `name` should be a resource name ending with `operations/{unique_id}`. + */ + 'name': (string); + /** + * Service-specific metadata associated with the operation. It typically + * contains progress information and common metadata such as create time. + * Some services might not provide such metadata. Any method that returns a + * long-running operation should document the metadata type, if any. + */ + 'metadata': (O_google_protobuf_Any | null); + /** + * If the value is `false`, it means the operation is still in progress. + * If `true`, the operation is completed, and either `error` or `response` is + * available. + */ + 'done': (boolean); + /** + * The error result of the operation in case of failure or cancellation. + */ + 'error'?: (O_google_rpc_Status | null); + /** + * The normal response of the operation in case of success. If the original + * method returns no data on success, such as `Delete`, the response is + * `google.protobuf.Empty`. If the original method is standard + * `Get`/`Create`/`Update`, the response should be the resource. For other + * methods, the response should have the type `XxxResponse`, where `Xxx` + * is the original method name. For example, if the original method name + * is `TakeSnapshot()`, the inferred response type is + * `TakeSnapshotResponse`. + */ + 'response'?: (O_google_protobuf_Any | null); + /** + * The operation result, which can be either an `error` or a valid `response`. + * If `done` == `false`, neither `error` nor `response` is set. + * If `done` == `true`, exactly one of `error` or `response` is set. + */ + 'result': "error"|"response"; +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/OperationInfo.ts b/packages/proto-loader/golden-generated/google/longrunning/OperationInfo.ts new file mode 100644 index 000000000..907574412 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/OperationInfo.ts @@ -0,0 +1,76 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + + +/** + * A message representing the message types used by a long-running operation. + * + * Example: + * + * rpc LongRunningRecognize(LongRunningRecognizeRequest) + * returns (google.longrunning.Operation) { + * option (google.longrunning.operation_info) = { + * response_type: "LongRunningRecognizeResponse" + * metadata_type: "LongRunningRecognizeMetadata" + * }; + * } + */ +export interface IOperationInfo { + /** + * Required. The message name of the primary return type for this + * long-running operation. + * This type will be used to deserialize the LRO's response. + * + * If the response is in a different package from the rpc, a fully-qualified + * message name must be used (e.g. `google.protobuf.Struct`). + * + * Note: Altering this value constitutes a breaking change. + */ + 'response_type'?: (string); + /** + * Required. The message name of the metadata type for this long-running + * operation. + * + * If the response is in a different package from the rpc, a fully-qualified + * message name must be used (e.g. `google.protobuf.Struct`). + * + * Note: Altering this value constitutes a breaking change. + */ + 'metadata_type'?: (string); +} + +/** + * A message representing the message types used by a long-running operation. + * + * Example: + * + * rpc LongRunningRecognize(LongRunningRecognizeRequest) + * returns (google.longrunning.Operation) { + * option (google.longrunning.operation_info) = { + * response_type: "LongRunningRecognizeResponse" + * metadata_type: "LongRunningRecognizeMetadata" + * }; + * } + */ +export interface OOperationInfo { + /** + * Required. The message name of the primary return type for this + * long-running operation. + * This type will be used to deserialize the LRO's response. + * + * If the response is in a different package from the rpc, a fully-qualified + * message name must be used (e.g. `google.protobuf.Struct`). + * + * Note: Altering this value constitutes a breaking change. + */ + 'response_type': (string); + /** + * Required. The message name of the metadata type for this long-running + * operation. + * + * If the response is in a different package from the rpc, a fully-qualified + * message name must be used (e.g. `google.protobuf.Struct`). + * + * Note: Altering this value constitutes a breaking change. + */ + 'metadata_type': (string); +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts new file mode 100644 index 000000000..00d6a95d2 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts @@ -0,0 +1,241 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { ICancelOperationRequest as I_google_longrunning_CancelOperationRequest, OCancelOperationRequest as O_google_longrunning_CancelOperationRequest } from '../../google/longrunning/CancelOperationRequest'; +import type { IDeleteOperationRequest as I_google_longrunning_DeleteOperationRequest, ODeleteOperationRequest as O_google_longrunning_DeleteOperationRequest } from '../../google/longrunning/DeleteOperationRequest'; +import type { IEmpty as I_google_protobuf_Empty, OEmpty as O_google_protobuf_Empty } from '../../google/protobuf/Empty'; +import type { IGetOperationRequest as I_google_longrunning_GetOperationRequest, OGetOperationRequest as O_google_longrunning_GetOperationRequest } from '../../google/longrunning/GetOperationRequest'; +import type { IListOperationsRequest as I_google_longrunning_ListOperationsRequest, OListOperationsRequest as O_google_longrunning_ListOperationsRequest } from '../../google/longrunning/ListOperationsRequest'; +import type { IListOperationsResponse as I_google_longrunning_ListOperationsResponse, OListOperationsResponse as O_google_longrunning_ListOperationsResponse } from '../../google/longrunning/ListOperationsResponse'; +import type { IOperation as I_google_longrunning_Operation, OOperation as O_google_longrunning_Operation } from '../../google/longrunning/Operation'; +import type { IWaitOperationRequest as I_google_longrunning_WaitOperationRequest, OWaitOperationRequest as O_google_longrunning_WaitOperationRequest } from '../../google/longrunning/WaitOperationRequest'; + +/** + * Manages long-running operations with an API service. + * + * When an API method normally takes long time to complete, it can be designed + * to return [Operation][google.longrunning.Operation] to the client, and the client can use this + * interface to receive the real response asynchronously by polling the + * operation resource, or pass the operation resource to another API (such as + * Google Cloud Pub/Sub API) to receive the response. Any API service that + * returns long-running operations should implement the `Operations` interface + * so developers can have a consistent client experience. + */ +export interface OperationsClient extends grpc.Client { + /** + * Starts asynchronous cancellation on a long-running operation. The server + * makes a best effort to cancel the operation, but success is not + * guaranteed. If the server doesn't support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. Clients can use + * [Operations.GetOperation][google.longrunning.Operations.GetOperation] or + * other methods to check whether the cancellation succeeded or whether the + * operation completed despite cancellation. On successful cancellation, + * the operation is not deleted; instead, it becomes an operation with + * an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, + * corresponding to `Code.CANCELLED`. + */ + CancelOperation(argument: I_google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + CancelOperation(argument: I_google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + CancelOperation(argument: I_google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + CancelOperation(argument: I_google_longrunning_CancelOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * Starts asynchronous cancellation on a long-running operation. The server + * makes a best effort to cancel the operation, but success is not + * guaranteed. If the server doesn't support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. Clients can use + * [Operations.GetOperation][google.longrunning.Operations.GetOperation] or + * other methods to check whether the cancellation succeeded or whether the + * operation completed despite cancellation. On successful cancellation, + * the operation is not deleted; instead, it becomes an operation with + * an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, + * corresponding to `Code.CANCELLED`. + */ + cancelOperation(argument: I_google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + cancelOperation(argument: I_google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + cancelOperation(argument: I_google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + cancelOperation(argument: I_google_longrunning_CancelOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + + /** + * Deletes a long-running operation. This method indicates that the client is + * no longer interested in the operation result. It does not cancel the + * operation. If the server doesn't support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. + */ + DeleteOperation(argument: I_google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + DeleteOperation(argument: I_google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + DeleteOperation(argument: I_google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + DeleteOperation(argument: I_google_longrunning_DeleteOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * Deletes a long-running operation. This method indicates that the client is + * no longer interested in the operation result. It does not cancel the + * operation. If the server doesn't support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. + */ + deleteOperation(argument: I_google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + deleteOperation(argument: I_google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + deleteOperation(argument: I_google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + deleteOperation(argument: I_google_longrunning_DeleteOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + + /** + * Gets the latest state of a long-running operation. Clients can use this + * method to poll the operation result at intervals as recommended by the API + * service. + */ + GetOperation(argument: I_google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + GetOperation(argument: I_google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + GetOperation(argument: I_google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + GetOperation(argument: I_google_longrunning_GetOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * Gets the latest state of a long-running operation. Clients can use this + * method to poll the operation result at intervals as recommended by the API + * service. + */ + getOperation(argument: I_google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + getOperation(argument: I_google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + getOperation(argument: I_google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + getOperation(argument: I_google_longrunning_GetOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + + /** + * Lists operations that match the specified filter in the request. If the + * server doesn't support this method, it returns `UNIMPLEMENTED`. + * + * NOTE: the `name` binding allows API services to override the binding + * to use different resource name schemes, such as `users/* /operations`. To + * override the binding, API services can add a binding such as + * `"/v1/{name=users/*}/operations"` to their service configuration. + * For backwards compatibility, the default name includes the operations + * collection id, however overriding users must ensure the name binding + * is the parent resource, without the operations collection id. + */ + ListOperations(argument: I_google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + ListOperations(argument: I_google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + ListOperations(argument: I_google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + ListOperations(argument: I_google_longrunning_ListOperationsRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * Lists operations that match the specified filter in the request. If the + * server doesn't support this method, it returns `UNIMPLEMENTED`. + * + * NOTE: the `name` binding allows API services to override the binding + * to use different resource name schemes, such as `users/* /operations`. To + * override the binding, API services can add a binding such as + * `"/v1/{name=users/*}/operations"` to their service configuration. + * For backwards compatibility, the default name includes the operations + * collection id, however overriding users must ensure the name binding + * is the parent resource, without the operations collection id. + */ + listOperations(argument: I_google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + listOperations(argument: I_google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + listOperations(argument: I_google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + listOperations(argument: I_google_longrunning_ListOperationsRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + + /** + * Waits for the specified long-running operation until it is done or reaches + * at most a specified timeout, returning the latest state. If the operation + * is already done, the latest state is immediately returned. If the timeout + * specified is greater than the default HTTP/RPC timeout, the HTTP/RPC + * timeout is used. If the server does not support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. + * Note that this method is on a best-effort basis. It may return the latest + * state before the specified timeout (including immediately), meaning even an + * immediate response is no guarantee that the operation is done. + */ + WaitOperation(argument: I_google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + WaitOperation(argument: I_google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + WaitOperation(argument: I_google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + WaitOperation(argument: I_google_longrunning_WaitOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * Waits for the specified long-running operation until it is done or reaches + * at most a specified timeout, returning the latest state. If the operation + * is already done, the latest state is immediately returned. If the timeout + * specified is greater than the default HTTP/RPC timeout, the HTTP/RPC + * timeout is used. If the server does not support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. + * Note that this method is on a best-effort basis. It may return the latest + * state before the specified timeout (including immediately), meaning even an + * immediate response is no guarantee that the operation is done. + */ + waitOperation(argument: I_google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + waitOperation(argument: I_google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + waitOperation(argument: I_google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + waitOperation(argument: I_google_longrunning_WaitOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + +} + +/** + * Manages long-running operations with an API service. + * + * When an API method normally takes long time to complete, it can be designed + * to return [Operation][google.longrunning.Operation] to the client, and the client can use this + * interface to receive the real response asynchronously by polling the + * operation resource, or pass the operation resource to another API (such as + * Google Cloud Pub/Sub API) to receive the response. Any API service that + * returns long-running operations should implement the `Operations` interface + * so developers can have a consistent client experience. + */ +export interface OperationsHandlers extends grpc.UntypedServiceImplementation { + /** + * Starts asynchronous cancellation on a long-running operation. The server + * makes a best effort to cancel the operation, but success is not + * guaranteed. If the server doesn't support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. Clients can use + * [Operations.GetOperation][google.longrunning.Operations.GetOperation] or + * other methods to check whether the cancellation succeeded or whether the + * operation completed despite cancellation. On successful cancellation, + * the operation is not deleted; instead, it becomes an operation with + * an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, + * corresponding to `Code.CANCELLED`. + */ + CancelOperation: grpc.handleUnaryCall; + + /** + * Deletes a long-running operation. This method indicates that the client is + * no longer interested in the operation result. It does not cancel the + * operation. If the server doesn't support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. + */ + DeleteOperation: grpc.handleUnaryCall; + + /** + * Gets the latest state of a long-running operation. Clients can use this + * method to poll the operation result at intervals as recommended by the API + * service. + */ + GetOperation: grpc.handleUnaryCall; + + /** + * Lists operations that match the specified filter in the request. If the + * server doesn't support this method, it returns `UNIMPLEMENTED`. + * + * NOTE: the `name` binding allows API services to override the binding + * to use different resource name schemes, such as `users/* /operations`. To + * override the binding, API services can add a binding such as + * `"/v1/{name=users/*}/operations"` to their service configuration. + * For backwards compatibility, the default name includes the operations + * collection id, however overriding users must ensure the name binding + * is the parent resource, without the operations collection id. + */ + ListOperations: grpc.handleUnaryCall; + + /** + * Waits for the specified long-running operation until it is done or reaches + * at most a specified timeout, returning the latest state. If the operation + * is already done, the latest state is immediately returned. If the timeout + * specified is greater than the default HTTP/RPC timeout, the HTTP/RPC + * timeout is used. If the server does not support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. + * Note that this method is on a best-effort basis. It may return the latest + * state before the specified timeout (including immediately), meaning even an + * immediate response is no guarantee that the operation is done. + */ + WaitOperation: grpc.handleUnaryCall; + +} + +export interface OperationsDefinition extends grpc.ServiceDefinition { + CancelOperation: MethodDefinition + DeleteOperation: MethodDefinition + GetOperation: MethodDefinition + ListOperations: MethodDefinition + WaitOperation: MethodDefinition +} diff --git a/packages/proto-loader/golden-generated/google/longrunning/WaitOperationRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/WaitOperationRequest.ts new file mode 100644 index 000000000..2f11f7580 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/longrunning/WaitOperationRequest.ts @@ -0,0 +1,35 @@ +// Original file: deps/googleapis/google/longrunning/operations.proto + +import type { IDuration as I_google_protobuf_Duration, ODuration as O_google_protobuf_Duration } from '../../google/protobuf/Duration'; + +/** + * The request message for [Operations.WaitOperation][google.longrunning.Operations.WaitOperation]. + */ +export interface IWaitOperationRequest { + /** + * The name of the operation resource to wait on. + */ + 'name'?: (string); + /** + * The maximum duration to wait before timing out. If left blank, the wait + * will be at most the time permitted by the underlying HTTP/RPC protocol. + * If RPC context deadline is also specified, the shorter one will be used. + */ + 'timeout'?: (I_google_protobuf_Duration | null); +} + +/** + * The request message for [Operations.WaitOperation][google.longrunning.Operations.WaitOperation]. + */ +export interface OWaitOperationRequest { + /** + * The name of the operation resource to wait on. + */ + 'name': (string); + /** + * The maximum duration to wait before timing out. If left blank, the wait + * will be at most the time permitted by the underlying HTTP/RPC protocol. + * If RPC context deadline is also specified, the shorter one will be used. + */ + 'timeout': (O_google_protobuf_Duration | null); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/Any.ts b/packages/proto-loader/golden-generated/google/protobuf/Any.ts new file mode 100644 index 000000000..d9ee4e200 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/Any.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { AnyExtension } from '@grpc/proto-loader'; + +export type IAny = AnyExtension | { + type_url: string; + value: Buffer | Uint8Array | string; +} + +export type OAny = AnyExtension | { + type_url: string; + value: Buffer; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/DescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/DescriptorProto.ts new file mode 100644 index 000000000..5f568ca2c --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/DescriptorProto.ts @@ -0,0 +1,53 @@ +// Original file: null + +import type { IFieldDescriptorProto as I_google_protobuf_FieldDescriptorProto, OFieldDescriptorProto as O_google_protobuf_FieldDescriptorProto } from '../../google/protobuf/FieldDescriptorProto'; +import type { IDescriptorProto as I_google_protobuf_DescriptorProto, ODescriptorProto as O_google_protobuf_DescriptorProto } from '../../google/protobuf/DescriptorProto'; +import type { IEnumDescriptorProto as I_google_protobuf_EnumDescriptorProto, OEnumDescriptorProto as O_google_protobuf_EnumDescriptorProto } from '../../google/protobuf/EnumDescriptorProto'; +import type { IMessageOptions as I_google_protobuf_MessageOptions, OMessageOptions as O_google_protobuf_MessageOptions } from '../../google/protobuf/MessageOptions'; +import type { IOneofDescriptorProto as I_google_protobuf_OneofDescriptorProto, OOneofDescriptorProto as O_google_protobuf_OneofDescriptorProto } from '../../google/protobuf/OneofDescriptorProto'; + +export interface I_google_protobuf_DescriptorProto_ExtensionRange { + 'start'?: (number); + 'end'?: (number); +} + +export interface O_google_protobuf_DescriptorProto_ExtensionRange { + 'start': (number); + 'end': (number); +} + +export interface I_google_protobuf_DescriptorProto_ReservedRange { + 'start'?: (number); + 'end'?: (number); +} + +export interface O_google_protobuf_DescriptorProto_ReservedRange { + 'start': (number); + 'end': (number); +} + +export interface IDescriptorProto { + 'name'?: (string); + 'field'?: (I_google_protobuf_FieldDescriptorProto)[]; + 'nestedType'?: (I_google_protobuf_DescriptorProto)[]; + 'enumType'?: (I_google_protobuf_EnumDescriptorProto)[]; + 'extensionRange'?: (I_google_protobuf_DescriptorProto_ExtensionRange)[]; + 'extension'?: (I_google_protobuf_FieldDescriptorProto)[]; + 'options'?: (I_google_protobuf_MessageOptions | null); + 'oneofDecl'?: (I_google_protobuf_OneofDescriptorProto)[]; + 'reservedRange'?: (I_google_protobuf_DescriptorProto_ReservedRange)[]; + 'reservedName'?: (string)[]; +} + +export interface ODescriptorProto { + 'name': (string); + 'field': (O_google_protobuf_FieldDescriptorProto)[]; + 'nestedType': (O_google_protobuf_DescriptorProto)[]; + 'enumType': (O_google_protobuf_EnumDescriptorProto)[]; + 'extensionRange': (O_google_protobuf_DescriptorProto_ExtensionRange)[]; + 'extension': (O_google_protobuf_FieldDescriptorProto)[]; + 'options': (O_google_protobuf_MessageOptions | null); + 'oneofDecl': (O_google_protobuf_OneofDescriptorProto)[]; + 'reservedRange': (O_google_protobuf_DescriptorProto_ReservedRange)[]; + 'reservedName': (string)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/Duration.ts b/packages/proto-loader/golden-generated/google/protobuf/Duration.ts new file mode 100644 index 000000000..d5e3be89a --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/Duration.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface IDuration { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface ODuration { + 'seconds': (string); + 'nanos': (number); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/Empty.ts b/packages/proto-loader/golden-generated/google/protobuf/Empty.ts new file mode 100644 index 000000000..6594cc86c --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/Empty.ts @@ -0,0 +1,8 @@ +// Original file: null + + +export interface IEmpty { +} + +export interface OEmpty { +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/EnumDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/EnumDescriptorProto.ts new file mode 100644 index 000000000..30f52c610 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/EnumDescriptorProto.ts @@ -0,0 +1,16 @@ +// Original file: null + +import type { IEnumValueDescriptorProto as I_google_protobuf_EnumValueDescriptorProto, OEnumValueDescriptorProto as O_google_protobuf_EnumValueDescriptorProto } from '../../google/protobuf/EnumValueDescriptorProto'; +import type { IEnumOptions as I_google_protobuf_EnumOptions, OEnumOptions as O_google_protobuf_EnumOptions } from '../../google/protobuf/EnumOptions'; + +export interface IEnumDescriptorProto { + 'name'?: (string); + 'value'?: (I_google_protobuf_EnumValueDescriptorProto)[]; + 'options'?: (I_google_protobuf_EnumOptions | null); +} + +export interface OEnumDescriptorProto { + 'name': (string); + 'value': (O_google_protobuf_EnumValueDescriptorProto)[]; + 'options': (O_google_protobuf_EnumOptions | null); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/EnumOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/EnumOptions.ts new file mode 100644 index 000000000..6d2a0c2c6 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/EnumOptions.ts @@ -0,0 +1,15 @@ +// Original file: null + +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; + +export interface IEnumOptions { + 'allowAlias'?: (boolean); + 'deprecated'?: (boolean); + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; +} + +export interface OEnumOptions { + 'allowAlias': (boolean); + 'deprecated': (boolean); + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/EnumValueDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/EnumValueDescriptorProto.ts new file mode 100644 index 000000000..44cfcde4a --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/EnumValueDescriptorProto.ts @@ -0,0 +1,15 @@ +// Original file: null + +import type { IEnumValueOptions as I_google_protobuf_EnumValueOptions, OEnumValueOptions as O_google_protobuf_EnumValueOptions } from '../../google/protobuf/EnumValueOptions'; + +export interface IEnumValueDescriptorProto { + 'name'?: (string); + 'number'?: (number); + 'options'?: (I_google_protobuf_EnumValueOptions | null); +} + +export interface OEnumValueDescriptorProto { + 'name': (string); + 'number': (number); + 'options': (O_google_protobuf_EnumValueOptions | null); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/EnumValueOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/EnumValueOptions.ts new file mode 100644 index 000000000..143381113 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/EnumValueOptions.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; + +export interface IEnumValueOptions { + 'deprecated'?: (boolean); + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; +} + +export interface OEnumValueOptions { + 'deprecated': (boolean); + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts new file mode 100644 index 000000000..1bcb69abe --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts @@ -0,0 +1,110 @@ +// Original file: null + +import type { IFieldOptions as I_google_protobuf_FieldOptions, OFieldOptions as O_google_protobuf_FieldOptions } from '../../google/protobuf/FieldOptions'; + +// Original file: null + +export const _google_protobuf_FieldDescriptorProto_Label = { + LABEL_OPTIONAL: 'LABEL_OPTIONAL', + LABEL_REQUIRED: 'LABEL_REQUIRED', + LABEL_REPEATED: 'LABEL_REPEATED', +} as const; + +export type I_google_protobuf_FieldDescriptorProto_Label = + | 'LABEL_OPTIONAL' + | 1 + | 'LABEL_REQUIRED' + | 2 + | 'LABEL_REPEATED' + | 3 + +export type O_google_protobuf_FieldDescriptorProto_Label = typeof _google_protobuf_FieldDescriptorProto_Label[keyof typeof _google_protobuf_FieldDescriptorProto_Label] + +// Original file: null + +export const _google_protobuf_FieldDescriptorProto_Type = { + TYPE_DOUBLE: 'TYPE_DOUBLE', + TYPE_FLOAT: 'TYPE_FLOAT', + TYPE_INT64: 'TYPE_INT64', + TYPE_UINT64: 'TYPE_UINT64', + TYPE_INT32: 'TYPE_INT32', + TYPE_FIXED64: 'TYPE_FIXED64', + TYPE_FIXED32: 'TYPE_FIXED32', + TYPE_BOOL: 'TYPE_BOOL', + TYPE_STRING: 'TYPE_STRING', + TYPE_GROUP: 'TYPE_GROUP', + TYPE_MESSAGE: 'TYPE_MESSAGE', + TYPE_BYTES: 'TYPE_BYTES', + TYPE_UINT32: 'TYPE_UINT32', + TYPE_ENUM: 'TYPE_ENUM', + TYPE_SFIXED32: 'TYPE_SFIXED32', + TYPE_SFIXED64: 'TYPE_SFIXED64', + TYPE_SINT32: 'TYPE_SINT32', + TYPE_SINT64: 'TYPE_SINT64', +} as const; + +export type I_google_protobuf_FieldDescriptorProto_Type = + | 'TYPE_DOUBLE' + | 1 + | 'TYPE_FLOAT' + | 2 + | 'TYPE_INT64' + | 3 + | 'TYPE_UINT64' + | 4 + | 'TYPE_INT32' + | 5 + | 'TYPE_FIXED64' + | 6 + | 'TYPE_FIXED32' + | 7 + | 'TYPE_BOOL' + | 8 + | 'TYPE_STRING' + | 9 + | 'TYPE_GROUP' + | 10 + | 'TYPE_MESSAGE' + | 11 + | 'TYPE_BYTES' + | 12 + | 'TYPE_UINT32' + | 13 + | 'TYPE_ENUM' + | 14 + | 'TYPE_SFIXED32' + | 15 + | 'TYPE_SFIXED64' + | 16 + | 'TYPE_SINT32' + | 17 + | 'TYPE_SINT64' + | 18 + +export type O_google_protobuf_FieldDescriptorProto_Type = typeof _google_protobuf_FieldDescriptorProto_Type[keyof typeof _google_protobuf_FieldDescriptorProto_Type] + +export interface IFieldDescriptorProto { + 'name'?: (string); + 'extendee'?: (string); + 'number'?: (number); + 'label'?: (I_google_protobuf_FieldDescriptorProto_Label); + 'type'?: (I_google_protobuf_FieldDescriptorProto_Type); + 'typeName'?: (string); + 'defaultValue'?: (string); + 'options'?: (I_google_protobuf_FieldOptions | null); + 'oneofIndex'?: (number); + 'jsonName'?: (string); +} + +export interface OFieldDescriptorProto { + 'name': (string); + 'extendee': (string); + 'number': (number); + 'label': (O_google_protobuf_FieldDescriptorProto_Label); + 'type': (O_google_protobuf_FieldDescriptorProto_Type); + 'typeName': (string); + 'defaultValue': (string); + 'options': (O_google_protobuf_FieldOptions | null); + 'oneofIndex': (number); + 'jsonName': (string); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts new file mode 100644 index 000000000..16e532d95 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts @@ -0,0 +1,62 @@ +// Original file: null + +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; +import type { IFieldBehavior as I_google_api_FieldBehavior, OFieldBehavior as O_google_api_FieldBehavior } from '../../google/api/FieldBehavior'; + +// Original file: null + +export const _google_protobuf_FieldOptions_CType = { + STRING: 'STRING', + CORD: 'CORD', + STRING_PIECE: 'STRING_PIECE', +} as const; + +export type I_google_protobuf_FieldOptions_CType = + | 'STRING' + | 0 + | 'CORD' + | 1 + | 'STRING_PIECE' + | 2 + +export type O_google_protobuf_FieldOptions_CType = typeof _google_protobuf_FieldOptions_CType[keyof typeof _google_protobuf_FieldOptions_CType] + +// Original file: null + +export const _google_protobuf_FieldOptions_JSType = { + JS_NORMAL: 'JS_NORMAL', + JS_STRING: 'JS_STRING', + JS_NUMBER: 'JS_NUMBER', +} as const; + +export type I_google_protobuf_FieldOptions_JSType = + | 'JS_NORMAL' + | 0 + | 'JS_STRING' + | 1 + | 'JS_NUMBER' + | 2 + +export type O_google_protobuf_FieldOptions_JSType = typeof _google_protobuf_FieldOptions_JSType[keyof typeof _google_protobuf_FieldOptions_JSType] + +export interface IFieldOptions { + 'ctype'?: (I_google_protobuf_FieldOptions_CType); + 'packed'?: (boolean); + 'deprecated'?: (boolean); + 'lazy'?: (boolean); + 'jstype'?: (I_google_protobuf_FieldOptions_JSType); + 'weak'?: (boolean); + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; + '.google.api.field_behavior'?: (I_google_api_FieldBehavior)[]; +} + +export interface OFieldOptions { + 'ctype': (O_google_protobuf_FieldOptions_CType); + 'packed': (boolean); + 'deprecated': (boolean); + 'lazy': (boolean); + 'jstype': (O_google_protobuf_FieldOptions_JSType); + 'weak': (boolean); + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; + '.google.api.field_behavior': (O_google_api_FieldBehavior)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorProto.ts new file mode 100644 index 000000000..c98732f9d --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorProto.ts @@ -0,0 +1,38 @@ +// Original file: null + +import type { IDescriptorProto as I_google_protobuf_DescriptorProto, ODescriptorProto as O_google_protobuf_DescriptorProto } from '../../google/protobuf/DescriptorProto'; +import type { IEnumDescriptorProto as I_google_protobuf_EnumDescriptorProto, OEnumDescriptorProto as O_google_protobuf_EnumDescriptorProto } from '../../google/protobuf/EnumDescriptorProto'; +import type { IServiceDescriptorProto as I_google_protobuf_ServiceDescriptorProto, OServiceDescriptorProto as O_google_protobuf_ServiceDescriptorProto } from '../../google/protobuf/ServiceDescriptorProto'; +import type { IFieldDescriptorProto as I_google_protobuf_FieldDescriptorProto, OFieldDescriptorProto as O_google_protobuf_FieldDescriptorProto } from '../../google/protobuf/FieldDescriptorProto'; +import type { IFileOptions as I_google_protobuf_FileOptions, OFileOptions as O_google_protobuf_FileOptions } from '../../google/protobuf/FileOptions'; +import type { ISourceCodeInfo as I_google_protobuf_SourceCodeInfo, OSourceCodeInfo as O_google_protobuf_SourceCodeInfo } from '../../google/protobuf/SourceCodeInfo'; + +export interface IFileDescriptorProto { + 'name'?: (string); + 'package'?: (string); + 'dependency'?: (string)[]; + 'messageType'?: (I_google_protobuf_DescriptorProto)[]; + 'enumType'?: (I_google_protobuf_EnumDescriptorProto)[]; + 'service'?: (I_google_protobuf_ServiceDescriptorProto)[]; + 'extension'?: (I_google_protobuf_FieldDescriptorProto)[]; + 'options'?: (I_google_protobuf_FileOptions | null); + 'sourceCodeInfo'?: (I_google_protobuf_SourceCodeInfo | null); + 'publicDependency'?: (number)[]; + 'weakDependency'?: (number)[]; + 'syntax'?: (string); +} + +export interface OFileDescriptorProto { + 'name': (string); + 'package': (string); + 'dependency': (string)[]; + 'messageType': (O_google_protobuf_DescriptorProto)[]; + 'enumType': (O_google_protobuf_EnumDescriptorProto)[]; + 'service': (O_google_protobuf_ServiceDescriptorProto)[]; + 'extension': (O_google_protobuf_FieldDescriptorProto)[]; + 'options': (O_google_protobuf_FileOptions | null); + 'sourceCodeInfo': (O_google_protobuf_SourceCodeInfo | null); + 'publicDependency': (number)[]; + 'weakDependency': (number)[]; + 'syntax': (string); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorSet.ts b/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorSet.ts new file mode 100644 index 000000000..9c940ed5e --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorSet.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { IFileDescriptorProto as I_google_protobuf_FileDescriptorProto, OFileDescriptorProto as O_google_protobuf_FileDescriptorProto } from '../../google/protobuf/FileDescriptorProto'; + +export interface IFileDescriptorSet { + 'file'?: (I_google_protobuf_FileDescriptorProto)[]; +} + +export interface OFileDescriptorSet { + 'file': (O_google_protobuf_FileDescriptorProto)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts new file mode 100644 index 000000000..fdeac9cd4 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts @@ -0,0 +1,57 @@ +// Original file: null + +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; + +// Original file: null + +export const _google_protobuf_FileOptions_OptimizeMode = { + SPEED: 'SPEED', + CODE_SIZE: 'CODE_SIZE', + LITE_RUNTIME: 'LITE_RUNTIME', +} as const; + +export type I_google_protobuf_FileOptions_OptimizeMode = + | 'SPEED' + | 1 + | 'CODE_SIZE' + | 2 + | 'LITE_RUNTIME' + | 3 + +export type O_google_protobuf_FileOptions_OptimizeMode = typeof _google_protobuf_FileOptions_OptimizeMode[keyof typeof _google_protobuf_FileOptions_OptimizeMode] + +export interface IFileOptions { + 'javaPackage'?: (string); + 'javaOuterClassname'?: (string); + 'optimizeFor'?: (I_google_protobuf_FileOptions_OptimizeMode); + 'javaMultipleFiles'?: (boolean); + 'goPackage'?: (string); + 'ccGenericServices'?: (boolean); + 'javaGenericServices'?: (boolean); + 'pyGenericServices'?: (boolean); + 'javaGenerateEqualsAndHash'?: (boolean); + 'deprecated'?: (boolean); + 'javaStringCheckUtf8'?: (boolean); + 'ccEnableArenas'?: (boolean); + 'objcClassPrefix'?: (string); + 'csharpNamespace'?: (string); + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; +} + +export interface OFileOptions { + 'javaPackage': (string); + 'javaOuterClassname': (string); + 'optimizeFor': (O_google_protobuf_FileOptions_OptimizeMode); + 'javaMultipleFiles': (boolean); + 'goPackage': (string); + 'ccGenericServices': (boolean); + 'javaGenericServices': (boolean); + 'pyGenericServices': (boolean); + 'javaGenerateEqualsAndHash': (boolean); + 'deprecated': (boolean); + 'javaStringCheckUtf8': (boolean); + 'ccEnableArenas': (boolean); + 'objcClassPrefix': (string); + 'csharpNamespace': (string); + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/GeneratedCodeInfo.ts b/packages/proto-loader/golden-generated/google/protobuf/GeneratedCodeInfo.ts new file mode 100644 index 000000000..62f9dc715 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/GeneratedCodeInfo.ts @@ -0,0 +1,24 @@ +// Original file: null + + +export interface I_google_protobuf_GeneratedCodeInfo_Annotation { + 'path'?: (number)[]; + 'sourceFile'?: (string); + 'begin'?: (number); + 'end'?: (number); +} + +export interface O_google_protobuf_GeneratedCodeInfo_Annotation { + 'path': (number)[]; + 'sourceFile': (string); + 'begin': (number); + 'end': (number); +} + +export interface IGeneratedCodeInfo { + 'annotation'?: (I_google_protobuf_GeneratedCodeInfo_Annotation)[]; +} + +export interface OGeneratedCodeInfo { + 'annotation': (O_google_protobuf_GeneratedCodeInfo_Annotation)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/MessageOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/MessageOptions.ts new file mode 100644 index 000000000..8c8885e63 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/MessageOptions.ts @@ -0,0 +1,19 @@ +// Original file: null + +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; + +export interface IMessageOptions { + 'messageSetWireFormat'?: (boolean); + 'noStandardDescriptorAccessor'?: (boolean); + 'deprecated'?: (boolean); + 'mapEntry'?: (boolean); + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; +} + +export interface OMessageOptions { + 'messageSetWireFormat': (boolean); + 'noStandardDescriptorAccessor': (boolean); + 'deprecated': (boolean); + 'mapEntry': (boolean); + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/MethodDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/MethodDescriptorProto.ts new file mode 100644 index 000000000..0826370df --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/MethodDescriptorProto.ts @@ -0,0 +1,21 @@ +// Original file: null + +import type { IMethodOptions as I_google_protobuf_MethodOptions, OMethodOptions as O_google_protobuf_MethodOptions } from '../../google/protobuf/MethodOptions'; + +export interface IMethodDescriptorProto { + 'name'?: (string); + 'inputType'?: (string); + 'outputType'?: (string); + 'options'?: (I_google_protobuf_MethodOptions | null); + 'clientStreaming'?: (boolean); + 'serverStreaming'?: (boolean); +} + +export interface OMethodDescriptorProto { + 'name': (string); + 'inputType': (string); + 'outputType': (string); + 'options': (O_google_protobuf_MethodOptions | null); + 'clientStreaming': (boolean); + 'serverStreaming': (boolean); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/MethodOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/MethodOptions.ts new file mode 100644 index 000000000..5f0b69008 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/MethodOptions.ts @@ -0,0 +1,21 @@ +// Original file: null + +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; +import type { IOperationInfo as I_google_longrunning_OperationInfo, OOperationInfo as O_google_longrunning_OperationInfo } from '../../google/longrunning/OperationInfo'; +import type { IHttpRule as I_google_api_HttpRule, OHttpRule as O_google_api_HttpRule } from '../../google/api/HttpRule'; + +export interface IMethodOptions { + 'deprecated'?: (boolean); + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; + '.google.longrunning.operation_info'?: (I_google_longrunning_OperationInfo | null); + '.google.api.method_signature'?: (string)[]; + '.google.api.http'?: (I_google_api_HttpRule | null); +} + +export interface OMethodOptions { + 'deprecated': (boolean); + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; + '.google.longrunning.operation_info': (O_google_longrunning_OperationInfo | null); + '.google.api.method_signature': (string)[]; + '.google.api.http': (O_google_api_HttpRule | null); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/OneofDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/OneofDescriptorProto.ts new file mode 100644 index 000000000..6394270ea --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/OneofDescriptorProto.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { IOneofOptions as I_google_protobuf_OneofOptions, OOneofOptions as O_google_protobuf_OneofOptions } from '../../google/protobuf/OneofOptions'; + +export interface IOneofDescriptorProto { + 'name'?: (string); + 'options'?: (I_google_protobuf_OneofOptions | null); +} + +export interface OOneofDescriptorProto { + 'name': (string); + 'options': (O_google_protobuf_OneofOptions | null); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/OneofOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/OneofOptions.ts new file mode 100644 index 000000000..73280ad73 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/OneofOptions.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; + +export interface IOneofOptions { + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; +} + +export interface OOneofOptions { + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/ServiceDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/ServiceDescriptorProto.ts new file mode 100644 index 000000000..a0427fda5 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/ServiceDescriptorProto.ts @@ -0,0 +1,16 @@ +// Original file: null + +import type { IMethodDescriptorProto as I_google_protobuf_MethodDescriptorProto, OMethodDescriptorProto as O_google_protobuf_MethodDescriptorProto } from '../../google/protobuf/MethodDescriptorProto'; +import type { IServiceOptions as I_google_protobuf_ServiceOptions, OServiceOptions as O_google_protobuf_ServiceOptions } from '../../google/protobuf/ServiceOptions'; + +export interface IServiceDescriptorProto { + 'name'?: (string); + 'method'?: (I_google_protobuf_MethodDescriptorProto)[]; + 'options'?: (I_google_protobuf_ServiceOptions | null); +} + +export interface OServiceDescriptorProto { + 'name': (string); + 'method': (O_google_protobuf_MethodDescriptorProto)[]; + 'options': (O_google_protobuf_ServiceOptions | null); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/ServiceOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/ServiceOptions.ts new file mode 100644 index 000000000..0ddc8e187 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/ServiceOptions.ts @@ -0,0 +1,17 @@ +// Original file: null + +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; + +export interface IServiceOptions { + 'deprecated'?: (boolean); + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; + '.google.api.default_host'?: (string); + '.google.api.oauth_scopes'?: (string); +} + +export interface OServiceOptions { + 'deprecated': (boolean); + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; + '.google.api.default_host': (string); + '.google.api.oauth_scopes': (string); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/SourceCodeInfo.ts b/packages/proto-loader/golden-generated/google/protobuf/SourceCodeInfo.ts new file mode 100644 index 000000000..4d0856604 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/SourceCodeInfo.ts @@ -0,0 +1,26 @@ +// Original file: null + + +export interface I_google_protobuf_SourceCodeInfo_Location { + 'path'?: (number)[]; + 'span'?: (number)[]; + 'leadingComments'?: (string); + 'trailingComments'?: (string); + 'leadingDetachedComments'?: (string)[]; +} + +export interface O_google_protobuf_SourceCodeInfo_Location { + 'path': (number)[]; + 'span': (number)[]; + 'leadingComments': (string); + 'trailingComments': (string); + 'leadingDetachedComments': (string)[]; +} + +export interface ISourceCodeInfo { + 'location'?: (I_google_protobuf_SourceCodeInfo_Location)[]; +} + +export interface OSourceCodeInfo { + 'location': (O_google_protobuf_SourceCodeInfo_Location)[]; +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/Timestamp.ts b/packages/proto-loader/golden-generated/google/protobuf/Timestamp.ts new file mode 100644 index 000000000..06d756134 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/Timestamp.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface ITimestamp { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface OTimestamp { + 'seconds': (string); + 'nanos': (number); +} diff --git a/packages/proto-loader/golden-generated/google/protobuf/UninterpretedOption.ts b/packages/proto-loader/golden-generated/google/protobuf/UninterpretedOption.ts new file mode 100644 index 000000000..fa0feaf52 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/protobuf/UninterpretedOption.ts @@ -0,0 +1,33 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface I_google_protobuf_UninterpretedOption_NamePart { + 'namePart'?: (string); + 'isExtension'?: (boolean); +} + +export interface O_google_protobuf_UninterpretedOption_NamePart { + 'namePart': (string); + 'isExtension': (boolean); +} + +export interface IUninterpretedOption { + 'name'?: (I_google_protobuf_UninterpretedOption_NamePart)[]; + 'identifierValue'?: (string); + 'positiveIntValue'?: (number | string | Long); + 'negativeIntValue'?: (number | string | Long); + 'doubleValue'?: (number | string); + 'stringValue'?: (Buffer | Uint8Array | string); + 'aggregateValue'?: (string); +} + +export interface OUninterpretedOption { + 'name': (O_google_protobuf_UninterpretedOption_NamePart)[]; + 'identifierValue': (string); + 'positiveIntValue': (string); + 'negativeIntValue': (string); + 'doubleValue': (number | string); + 'stringValue': (Buffer); + 'aggregateValue': (string); +} diff --git a/packages/proto-loader/golden-generated/google/rpc/Status.ts b/packages/proto-loader/golden-generated/google/rpc/Status.ts new file mode 100644 index 000000000..05cf71c5f --- /dev/null +++ b/packages/proto-loader/golden-generated/google/rpc/Status.ts @@ -0,0 +1,57 @@ +// Original file: deps/googleapis/google/rpc/status.proto + +import type { IAny as I_google_protobuf_Any, OAny as O_google_protobuf_Any } from '../../google/protobuf/Any'; + +/** + * The `Status` type defines a logical error model that is suitable for + * different programming environments, including REST APIs and RPC APIs. It is + * used by [gRPC](https://github.com/grpc). Each `Status` message contains + * three pieces of data: error code, error message, and error details. + * + * You can find out more about this error model and how to work with it in the + * [API Design Guide](https://cloud.google.com/apis/design/errors). + */ +export interface IStatus { + /** + * The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + 'code'?: (number); + /** + * A developer-facing error message, which should be in English. Any + * user-facing error message should be localized and sent in the + * [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + */ + 'message'?: (string); + /** + * A list of messages that carry the error details. There is a common set of + * message types for APIs to use. + */ + 'details'?: (I_google_protobuf_Any)[]; +} + +/** + * The `Status` type defines a logical error model that is suitable for + * different programming environments, including REST APIs and RPC APIs. It is + * used by [gRPC](https://github.com/grpc). Each `Status` message contains + * three pieces of data: error code, error message, and error details. + * + * You can find out more about this error model and how to work with it in the + * [API Design Guide](https://cloud.google.com/apis/design/errors). + */ +export interface OStatus { + /** + * The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + 'code': (number); + /** + * A developer-facing error message, which should be in English. Any + * user-facing error message should be localized and sent in the + * [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + */ + 'message': (string); + /** + * A list of messages that carry the error details. There is a common set of + * message types for APIs to use. + */ + 'details': (O_google_protobuf_Any)[]; +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockRequest.ts new file mode 100644 index 000000000..29d10f6dd --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockRequest.ts @@ -0,0 +1,45 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +import type { IDuration as I_google_protobuf_Duration, ODuration as O_google_protobuf_Duration } from '../../../google/protobuf/Duration'; +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; +import type { IBlockResponse as I_google_showcase_v1beta1_BlockResponse, OBlockResponse as O_google_showcase_v1beta1_BlockResponse } from '../../../google/showcase/v1beta1/BlockResponse'; + +/** + * The request for Block method. + */ +export interface IBlockRequest { + /** + * The amount of time to block before returning a response. + */ + 'response_delay'?: (I_google_protobuf_Duration | null); + /** + * The error that will be returned by the server. If this code is specified + * to be the OK rpc code, an empty response will be returned. + */ + 'error'?: (I_google_rpc_Status | null); + /** + * The response to be returned that will signify successful method call. + */ + 'success'?: (I_google_showcase_v1beta1_BlockResponse | null); + 'response'?: "error"|"success"; +} + +/** + * The request for Block method. + */ +export interface OBlockRequest { + /** + * The amount of time to block before returning a response. + */ + 'response_delay': (O_google_protobuf_Duration | null); + /** + * The error that will be returned by the server. If this code is specified + * to be the OK rpc code, an empty response will be returned. + */ + 'error'?: (O_google_rpc_Status | null); + /** + * The response to be returned that will signify successful method call. + */ + 'success'?: (O_google_showcase_v1beta1_BlockResponse | null); + 'response': "error"|"success"; +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockResponse.ts new file mode 100644 index 000000000..3bb9bddf2 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockResponse.ts @@ -0,0 +1,24 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + + +/** + * The response for Block method. + */ +export interface IBlockResponse { + /** + * This content can contain anything, the server will not depend on a value + * here. + */ + 'content'?: (string); +} + +/** + * The response for Block method. + */ +export interface OBlockResponse { + /** + * This content can contain anything, the server will not depend on a value + * here. + */ + 'content': (string); +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts new file mode 100644 index 000000000..a0330fe68 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts @@ -0,0 +1,202 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { IBlockRequest as I_google_showcase_v1beta1_BlockRequest, OBlockRequest as O_google_showcase_v1beta1_BlockRequest } from '../../../google/showcase/v1beta1/BlockRequest'; +import type { IBlockResponse as I_google_showcase_v1beta1_BlockResponse, OBlockResponse as O_google_showcase_v1beta1_BlockResponse } from '../../../google/showcase/v1beta1/BlockResponse'; +import type { IEchoRequest as I_google_showcase_v1beta1_EchoRequest, OEchoRequest as O_google_showcase_v1beta1_EchoRequest } from '../../../google/showcase/v1beta1/EchoRequest'; +import type { IEchoResponse as I_google_showcase_v1beta1_EchoResponse, OEchoResponse as O_google_showcase_v1beta1_EchoResponse } from '../../../google/showcase/v1beta1/EchoResponse'; +import type { IExpandRequest as I_google_showcase_v1beta1_ExpandRequest, OExpandRequest as O_google_showcase_v1beta1_ExpandRequest } from '../../../google/showcase/v1beta1/ExpandRequest'; +import type { IOperation as I_google_longrunning_Operation, OOperation as O_google_longrunning_Operation } from '../../../google/longrunning/Operation'; +import type { IPagedExpandRequest as I_google_showcase_v1beta1_PagedExpandRequest, OPagedExpandRequest as O_google_showcase_v1beta1_PagedExpandRequest } from '../../../google/showcase/v1beta1/PagedExpandRequest'; +import type { IPagedExpandResponse as I_google_showcase_v1beta1_PagedExpandResponse, OPagedExpandResponse as O_google_showcase_v1beta1_PagedExpandResponse } from '../../../google/showcase/v1beta1/PagedExpandResponse'; +import type { IWaitRequest as I_google_showcase_v1beta1_WaitRequest, OWaitRequest as O_google_showcase_v1beta1_WaitRequest } from '../../../google/showcase/v1beta1/WaitRequest'; + +/** + * This service is used showcase the four main types of rpcs - unary, server + * side streaming, client side streaming, and bidirectional streaming. This + * service also exposes methods that explicitly implement server delay, and + * paginated calls. Set the 'showcase-trailer' metadata key on any method + * to have the values echoed in the response trailers. + */ +export interface EchoClient extends grpc.Client { + /** + * This method will block (wait) for the requested amount of time + * and then return the response or error. + * This method showcases how a client handles delays or retries. + */ + Block(argument: I_google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Block(argument: I_google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Block(argument: I_google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Block(argument: I_google_showcase_v1beta1_BlockRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * This method will block (wait) for the requested amount of time + * and then return the response or error. + * This method showcases how a client handles delays or retries. + */ + block(argument: I_google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + block(argument: I_google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + block(argument: I_google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + block(argument: I_google_showcase_v1beta1_BlockRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + + /** + * This method, upon receiving a request on the stream, the same content will + * be passed back on the stream. This method showcases bidirectional + * streaming rpcs. + */ + Chat(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream; + Chat(options?: grpc.CallOptions): grpc.ClientDuplexStream; + /** + * This method, upon receiving a request on the stream, the same content will + * be passed back on the stream. This method showcases bidirectional + * streaming rpcs. + */ + chat(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream; + chat(options?: grpc.CallOptions): grpc.ClientDuplexStream; + + /** + * This method will collect the words given to it. When the stream is closed + * by the client, this method will return the a concatenation of the strings + * passed to it. This method showcases client-side streaming rpcs. + */ + Collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientWritableStream; + Collect(metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientWritableStream; + Collect(options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientWritableStream; + Collect(callback: grpc.requestCallback): grpc.ClientWritableStream; + /** + * This method will collect the words given to it. When the stream is closed + * by the client, this method will return the a concatenation of the strings + * passed to it. This method showcases client-side streaming rpcs. + */ + collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientWritableStream; + collect(metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientWritableStream; + collect(options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientWritableStream; + collect(callback: grpc.requestCallback): grpc.ClientWritableStream; + + /** + * This method simply echos the request. This method is showcases unary rpcs. + */ + Echo(argument: I_google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Echo(argument: I_google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Echo(argument: I_google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Echo(argument: I_google_showcase_v1beta1_EchoRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * This method simply echos the request. This method is showcases unary rpcs. + */ + echo(argument: I_google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + echo(argument: I_google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + echo(argument: I_google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + echo(argument: I_google_showcase_v1beta1_EchoRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + + /** + * This method split the given content into words and will pass each word back + * through the stream. This method showcases server-side streaming rpcs. + */ + Expand(argument: I_google_showcase_v1beta1_ExpandRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream; + Expand(argument: I_google_showcase_v1beta1_ExpandRequest, options?: grpc.CallOptions): grpc.ClientReadableStream; + /** + * This method split the given content into words and will pass each word back + * through the stream. This method showcases server-side streaming rpcs. + */ + expand(argument: I_google_showcase_v1beta1_ExpandRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream; + expand(argument: I_google_showcase_v1beta1_ExpandRequest, options?: grpc.CallOptions): grpc.ClientReadableStream; + + /** + * This is similar to the Expand method but instead of returning a stream of + * expanded words, this method returns a paged list of expanded words. + */ + PagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + PagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + PagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + PagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * This is similar to the Expand method but instead of returning a stream of + * expanded words, this method returns a paged list of expanded words. + */ + pagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + pagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + pagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + pagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + + /** + * This method will wait the requested amount of and then return. + * This method showcases how a client handles a request timing out. + */ + Wait(argument: I_google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Wait(argument: I_google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Wait(argument: I_google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Wait(argument: I_google_showcase_v1beta1_WaitRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + /** + * This method will wait the requested amount of and then return. + * This method showcases how a client handles a request timing out. + */ + wait(argument: I_google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + wait(argument: I_google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + wait(argument: I_google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + wait(argument: I_google_showcase_v1beta1_WaitRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; + +} + +/** + * This service is used showcase the four main types of rpcs - unary, server + * side streaming, client side streaming, and bidirectional streaming. This + * service also exposes methods that explicitly implement server delay, and + * paginated calls. Set the 'showcase-trailer' metadata key on any method + * to have the values echoed in the response trailers. + */ +export interface EchoHandlers extends grpc.UntypedServiceImplementation { + /** + * This method will block (wait) for the requested amount of time + * and then return the response or error. + * This method showcases how a client handles delays or retries. + */ + Block: grpc.handleUnaryCall; + + /** + * This method, upon receiving a request on the stream, the same content will + * be passed back on the stream. This method showcases bidirectional + * streaming rpcs. + */ + Chat: grpc.handleBidiStreamingCall; + + /** + * This method will collect the words given to it. When the stream is closed + * by the client, this method will return the a concatenation of the strings + * passed to it. This method showcases client-side streaming rpcs. + */ + Collect: grpc.handleClientStreamingCall; + + /** + * This method simply echos the request. This method is showcases unary rpcs. + */ + Echo: grpc.handleUnaryCall; + + /** + * This method split the given content into words and will pass each word back + * through the stream. This method showcases server-side streaming rpcs. + */ + Expand: grpc.handleServerStreamingCall; + + /** + * This is similar to the Expand method but instead of returning a stream of + * expanded words, this method returns a paged list of expanded words. + */ + PagedExpand: grpc.handleUnaryCall; + + /** + * This method will wait the requested amount of and then return. + * This method showcases how a client handles a request timing out. + */ + Wait: grpc.handleUnaryCall; + +} + +export interface EchoDefinition extends grpc.ServiceDefinition { + Block: MethodDefinition + Chat: MethodDefinition + Collect: MethodDefinition + Echo: MethodDefinition + Expand: MethodDefinition + PagedExpand: MethodDefinition + Wait: MethodDefinition +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts new file mode 100644 index 000000000..a5fb8f766 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts @@ -0,0 +1,48 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; +import type { ISeverity as I_google_showcase_v1beta1_Severity, OSeverity as O_google_showcase_v1beta1_Severity } from '../../../google/showcase/v1beta1/Severity'; + +/** + * The request message used for the Echo, Collect and Chat methods. + * If content or opt are set in this message then the request will succeed. + * If status is set in this message + * then the status will be returned as an error. + */ +export interface IEchoRequest { + /** + * The content to be echoed by the server. + */ + 'content'?: (string); + /** + * The error to be thrown by the server. + */ + 'error'?: (I_google_rpc_Status | null); + /** + * The severity to be echoed by the server. + */ + 'severity'?: (I_google_showcase_v1beta1_Severity); + 'response'?: "content"|"error"; +} + +/** + * The request message used for the Echo, Collect and Chat methods. + * If content or opt are set in this message then the request will succeed. + * If status is set in this message + * then the status will be returned as an error. + */ +export interface OEchoRequest { + /** + * The content to be echoed by the server. + */ + 'content'?: (string); + /** + * The error to be thrown by the server. + */ + 'error'?: (O_google_rpc_Status | null); + /** + * The severity to be echoed by the server. + */ + 'severity': (O_google_showcase_v1beta1_Severity); + 'response': "content"|"error"; +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts new file mode 100644 index 000000000..ac50115bf --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts @@ -0,0 +1,31 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +import type { ISeverity as I_google_showcase_v1beta1_Severity, OSeverity as O_google_showcase_v1beta1_Severity } from '../../../google/showcase/v1beta1/Severity'; + +/** + * The response message for the Echo methods. + */ +export interface IEchoResponse { + /** + * The content specified in the request. + */ + 'content'?: (string); + /** + * The severity specified in the request. + */ + 'severity'?: (I_google_showcase_v1beta1_Severity); +} + +/** + * The response message for the Echo methods. + */ +export interface OEchoResponse { + /** + * The content specified in the request. + */ + 'content': (string); + /** + * The severity specified in the request. + */ + 'severity': (O_google_showcase_v1beta1_Severity); +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/ExpandRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/ExpandRequest.ts new file mode 100644 index 000000000..4347a617a --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/ExpandRequest.ts @@ -0,0 +1,31 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; + +/** + * The request message for the Expand method. + */ +export interface IExpandRequest { + /** + * The content that will be split into words and returned on the stream. + */ + 'content'?: (string); + /** + * The error that is thrown after all words are sent on the stream. + */ + 'error'?: (I_google_rpc_Status | null); +} + +/** + * The request message for the Expand method. + */ +export interface OExpandRequest { + /** + * The content that will be split into words and returned on the stream. + */ + 'content': (string); + /** + * The error that is thrown after all words are sent on the stream. + */ + 'error': (O_google_rpc_Status | null); +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandRequest.ts new file mode 100644 index 000000000..8c68ba990 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandRequest.ts @@ -0,0 +1,38 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + + +/** + * The request for the PagedExpand method. + */ +export interface IPagedExpandRequest { + /** + * The string to expand. + */ + 'content'?: (string); + /** + * The amount of words to returned in each page. + */ + 'page_size'?: (number); + /** + * The position of the page to be returned. + */ + 'page_token'?: (string); +} + +/** + * The request for the PagedExpand method. + */ +export interface OPagedExpandRequest { + /** + * The string to expand. + */ + 'content': (string); + /** + * The amount of words to returned in each page. + */ + 'page_size': (number); + /** + * The position of the page to be returned. + */ + 'page_token': (string); +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandResponse.ts new file mode 100644 index 000000000..3b3ef90c2 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandResponse.ts @@ -0,0 +1,31 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +import type { IEchoResponse as I_google_showcase_v1beta1_EchoResponse, OEchoResponse as O_google_showcase_v1beta1_EchoResponse } from '../../../google/showcase/v1beta1/EchoResponse'; + +/** + * The response for the PagedExpand method. + */ +export interface IPagedExpandResponse { + /** + * The words that were expanded. + */ + 'responses'?: (I_google_showcase_v1beta1_EchoResponse)[]; + /** + * The next page token. + */ + 'next_page_token'?: (string); +} + +/** + * The response for the PagedExpand method. + */ +export interface OPagedExpandResponse { + /** + * The words that were expanded. + */ + 'responses': (O_google_showcase_v1beta1_EchoResponse)[]; + /** + * The next page token. + */ + 'next_page_token': (string); +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Severity.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Severity.ts new file mode 100644 index 000000000..d109fe1ce --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Severity.ts @@ -0,0 +1,29 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +/** + * A severity enum used to test enum capabilities in GAPIC surfaces + */ +export const Severity = { + UNNECESSARY: 'UNNECESSARY', + NECESSARY: 'NECESSARY', + URGENT: 'URGENT', + CRITICAL: 'CRITICAL', +} as const; + +/** + * A severity enum used to test enum capabilities in GAPIC surfaces + */ +export type ISeverity = + | 'UNNECESSARY' + | 0 + | 'NECESSARY' + | 1 + | 'URGENT' + | 2 + | 'CRITICAL' + | 3 + +/** + * A severity enum used to test enum capabilities in GAPIC surfaces + */ +export type OSeverity = typeof Severity[keyof typeof Severity] diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitMetadata.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitMetadata.ts new file mode 100644 index 000000000..ddbe77c22 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitMetadata.ts @@ -0,0 +1,23 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +import type { ITimestamp as I_google_protobuf_Timestamp, OTimestamp as O_google_protobuf_Timestamp } from '../../../google/protobuf/Timestamp'; + +/** + * The metadata for Wait operation. + */ +export interface IWaitMetadata { + /** + * The time that this operation will complete. + */ + 'end_time'?: (I_google_protobuf_Timestamp | null); +} + +/** + * The metadata for Wait operation. + */ +export interface OWaitMetadata { + /** + * The time that this operation will complete. + */ + 'end_time': (O_google_protobuf_Timestamp | null); +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitRequest.ts new file mode 100644 index 000000000..331a66947 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitRequest.ts @@ -0,0 +1,56 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + +import type { ITimestamp as I_google_protobuf_Timestamp, OTimestamp as O_google_protobuf_Timestamp } from '../../../google/protobuf/Timestamp'; +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; +import type { IWaitResponse as I_google_showcase_v1beta1_WaitResponse, OWaitResponse as O_google_showcase_v1beta1_WaitResponse } from '../../../google/showcase/v1beta1/WaitResponse'; +import type { IDuration as I_google_protobuf_Duration, ODuration as O_google_protobuf_Duration } from '../../../google/protobuf/Duration'; + +/** + * The request for Wait method. + */ +export interface IWaitRequest { + /** + * The time that this operation will complete. + */ + 'end_time'?: (I_google_protobuf_Timestamp | null); + /** + * The error that will be returned by the server. If this code is specified + * to be the OK rpc code, an empty response will be returned. + */ + 'error'?: (I_google_rpc_Status | null); + /** + * The response to be returned on operation completion. + */ + 'success'?: (I_google_showcase_v1beta1_WaitResponse | null); + /** + * The duration of this operation. + */ + 'ttl'?: (I_google_protobuf_Duration | null); + 'end'?: "end_time"|"ttl"; + 'response'?: "error"|"success"; +} + +/** + * The request for Wait method. + */ +export interface OWaitRequest { + /** + * The time that this operation will complete. + */ + 'end_time'?: (O_google_protobuf_Timestamp | null); + /** + * The error that will be returned by the server. If this code is specified + * to be the OK rpc code, an empty response will be returned. + */ + 'error'?: (O_google_rpc_Status | null); + /** + * The response to be returned on operation completion. + */ + 'success'?: (O_google_showcase_v1beta1_WaitResponse | null); + /** + * The duration of this operation. + */ + 'ttl'?: (O_google_protobuf_Duration | null); + 'end': "end_time"|"ttl"; + 'response': "error"|"success"; +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitResponse.ts new file mode 100644 index 000000000..667b450e2 --- /dev/null +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitResponse.ts @@ -0,0 +1,22 @@ +// Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto + + +/** + * The result of the Wait operation. + */ +export interface IWaitResponse { + /** + * This content of the result. + */ + 'content'?: (string); +} + +/** + * The result of the Wait operation. + */ +export interface OWaitResponse { + /** + * This content of the result. + */ + 'content': (string); +} diff --git a/packages/proto-loader/gulpfile.ts b/packages/proto-loader/gulpfile.ts new file mode 100644 index 000000000..f8d47c800 --- /dev/null +++ b/packages/proto-loader/gulpfile.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2017 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. + * + */ + +import * as gulp from 'gulp'; + +import * as fs from 'fs'; +import * as mocha from 'gulp-mocha'; +import * as path from 'path'; +import * as execa from 'execa'; +import * as semver from 'semver'; + +Error.stackTraceLimit = Infinity; + +const protojsDir = __dirname; +const tslintPath = path.resolve(protojsDir, 'node_modules/google-ts-style/tslint.json'); +const tsconfigPath = path.resolve(protojsDir, 'tsconfig.json'); +const outDir = path.resolve(protojsDir, 'build'); +const srcDir = path.resolve(protojsDir, 'src'); +const testDir = path.resolve(protojsDir, 'test'); + +const execNpmVerb = (verb: string, ...args: string[]) => + execa('npm', [verb, ...args], {cwd: protojsDir, stdio: 'inherit'}); +const execNpmCommand = execNpmVerb.bind(null, 'run'); + +const install = () => execNpmVerb('install', '--unsafe-perm'); + +/** + * Runs tslint on files in src/, with linting rules defined in tslint.json. + */ +const lint = () => execNpmCommand('check'); + +const cleanFiles = () => execNpmCommand('clean'); + +const clean = gulp.series(install, cleanFiles); + +const cleanAll = gulp.parallel(clean); + +/** + * Transpiles TypeScript files in src/ and test/ to JavaScript according to the settings + * found in tsconfig.json. + */ +const compile = () => execNpmCommand('compile'); + +/** + * Transpiles src/ and test/, and then runs all tests. + */ +const runTests = () => { + if (semver.satisfies(process.version, ">=6")) { + return gulp.src(`${outDir}/test/**/*.js`) + .pipe(mocha({reporter: 'mocha-jenkins-reporter', + require: ['ts-node/register']})); + } else { + console.log(`Skipping proto-loader tests for Node ${process.version}`); + return Promise.resolve(null); + } +} + +const testGeneratorGolden = () => { + if (semver.satisfies(process.version, ">=10")) { + return execNpmCommand('validate-golden'); + } else { + console.log(`Skipping generator test for Node ${process.version}`); + return Promise.resolve(null); + } +} + +const test = gulp.series(install, runTests, testGeneratorGolden); + +export { + install, + lint, + clean, + cleanAll, + compile, + test +} diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json new file mode 100644 index 000000000..cae7635f6 --- /dev/null +++ b/packages/proto-loader/package.json @@ -0,0 +1,68 @@ +{ + "name": "@grpc/proto-loader", + "version": "0.7.3", + "author": "Google Inc.", + "contributors": [ + { + "name": "Michael Lumish", + "email": "mlumish@google.com" + } + ], + "description": "gRPC utility library for loading .proto files", + "homepage": "https://grpc.io/", + "main": "build/src/index.js", + "typings": "build/src/index.d.ts", + "scripts": { + "build": "npm run compile", + "clean": "rimraf ./build", + "compile": "tsc -p .", + "format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts", + "lint": "tslint -c node_modules/google-ts-style/tslint.json -p . -t codeFrame --type-check", + "prepare": "npm run compile", + "test": "gulp test", + "check": "gts check", + "fix": "gts fix", + "pretest": "npm run compile", + "posttest": "npm run check", + "generate-golden": "node ./build/bin/proto-loader-gen-types.js --keepCase --longs=String --enums=String --defaults --oneofs --json --includeComments --inputTemplate=I%s --outputTemplate=O%s -I deps/gapic-showcase/schema/ deps/googleapis/ -O ./golden-generated --grpcLib @grpc/grpc-js google/showcase/v1beta1/echo.proto", + "validate-golden": "rm -rf ./golden-generated-old && mv ./golden-generated/ ./golden-generated-old && npm run generate-golden && diff -rb ./golden-generated ./golden-generated-old" + }, + "repository": { + "type": "git", + "url": "https://github.com/grpc/grpc-node.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/grpc/grpc-node/issues" + }, + "files": [ + "LICENSE", + "build/src/*.d.ts", + "build/src/*.{js,js.map}", + "build/bin/*.{js,js.map}" + ], + "bin": { + "proto-loader-gen-types": "./build/bin/proto-loader-gen-types.js" + }, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "devDependencies": { + "@types/lodash.camelcase": "^4.3.4", + "@types/mkdirp": "^1.0.1", + "@types/mocha": "^5.2.7", + "@types/node": "^10.17.26", + "@types/yargs": "^16.0.4", + "clang-format": "^1.2.2", + "gts": "^3.1.0", + "rimraf": "^3.0.2", + "typescript": "~4.7.4" + }, + "engines": { + "node": ">=6" + } +} diff --git a/packages/proto-loader/prettier.config.js b/packages/proto-loader/prettier.config.js new file mode 100644 index 000000000..92747c8cc --- /dev/null +++ b/packages/proto-loader/prettier.config.js @@ -0,0 +1,5 @@ +module.exports = { + proseWrap: 'always', + singleQuote: true, + trailingComma: 'es5', +}; diff --git a/packages/proto-loader/src/index.ts b/packages/proto-loader/src/index.ts new file mode 100644 index 000000000..d607668a9 --- /dev/null +++ b/packages/proto-loader/src/index.ts @@ -0,0 +1,433 @@ +/** + * @license + * Copyright 2018 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. + * + */ + +import camelCase = require('lodash.camelcase'); +import * as Protobuf from 'protobufjs'; +import * as descriptor from 'protobufjs/ext/descriptor'; + +import { loadProtosWithOptionsSync, loadProtosWithOptions, Options, addCommonProtos } from './util'; + +import Long = require('long'); + +export { Options, Long }; + +/** + * This type exists for use with code generated by the proto-loader-gen-types + * tool. This type should be used with another interface, e.g. + * MessageType & AnyExtension for an object that is converted to or from a + * google.protobuf.Any message. + * For example, when processing an Any message: + * + * ```ts + * if (isAnyExtension(message)) { + * switch (message['@type']) { + * case TYPE1_URL: + * handleType1(message as AnyExtension & Type1); + * break; + * case TYPE2_URL: + * handleType2(message as AnyExtension & Type2); + * break; + * // ... + * } + * } + * ``` + */ +export interface AnyExtension { + /** + * The fully qualified name of the message type that this object represents, + * possibly including a URL prefix. + */ + '@type': string; +} + +export function isAnyExtension(obj: object): obj is AnyExtension { + return ('@type' in obj) && (typeof (obj as AnyExtension)['@type'] === 'string'); +} + +declare module 'protobufjs' { + interface Type { + toDescriptor( + protoVersion: string + ): Protobuf.Message & + descriptor.IDescriptorProto; + } + + interface RootConstructor { + new (options?: Options): Root; + fromDescriptor( + descriptorSet: + | descriptor.IFileDescriptorSet + | Protobuf.Reader + | Uint8Array + ): Root; + fromJSON(json: Protobuf.INamespace, root?: Root): Root; + } + + interface Root { + toDescriptor( + protoVersion: string + ): Protobuf.Message & + descriptor.IFileDescriptorSet; + } + + interface Enum { + toDescriptor( + protoVersion: string + ): Protobuf.Message & + descriptor.IEnumDescriptorProto; + } +} + +export interface Serialize { + (value: T): Buffer; +} + +export interface Deserialize { + (bytes: Buffer): T; +} + +export interface ProtobufTypeDefinition { + format: string; + type: object; + fileDescriptorProtos: Buffer[]; +} + +export interface MessageTypeDefinition extends ProtobufTypeDefinition { + format: 'Protocol Buffer 3 DescriptorProto'; +} + +export interface EnumTypeDefinition extends ProtobufTypeDefinition { + format: 'Protocol Buffer 3 EnumDescriptorProto'; +} + +export interface MethodDefinition { + path: string; + requestStream: boolean; + responseStream: boolean; + requestSerialize: Serialize; + responseSerialize: Serialize; + requestDeserialize: Deserialize; + responseDeserialize: Deserialize; + originalName?: string; + requestType: MessageTypeDefinition; + responseType: MessageTypeDefinition; +} + +export interface ServiceDefinition { + [index: string]: MethodDefinition; +} + +export type AnyDefinition = + | ServiceDefinition + | MessageTypeDefinition + | EnumTypeDefinition; + +export interface PackageDefinition { + [index: string]: AnyDefinition; +} + +type DecodedDescriptorSet = Protobuf.Message & + descriptor.IFileDescriptorSet; + +const descriptorOptions: Protobuf.IConversionOptions = { + longs: String, + enums: String, + bytes: String, + defaults: true, + oneofs: true, + json: true, +}; + +function joinName(baseName: string, name: string): string { + if (baseName === '') { + return name; + } else { + return baseName + '.' + name; + } +} + +type HandledReflectionObject = Protobuf.Service | Protobuf.Type | Protobuf.Enum; + +function isHandledReflectionObject( + obj: Protobuf.ReflectionObject +): obj is HandledReflectionObject { + return ( + obj instanceof Protobuf.Service || + obj instanceof Protobuf.Type || + obj instanceof Protobuf.Enum + ); +} + +function isNamespaceBase( + obj: Protobuf.ReflectionObject +): obj is Protobuf.NamespaceBase { + return obj instanceof Protobuf.Namespace || obj instanceof Protobuf.Root; +} + +function getAllHandledReflectionObjects( + obj: Protobuf.ReflectionObject, + parentName: string +): Array<[string, HandledReflectionObject]> { + const objName = joinName(parentName, obj.name); + if (isHandledReflectionObject(obj)) { + return [[objName, obj]]; + } else { + if (isNamespaceBase(obj) && typeof obj.nested !== 'undefined') { + return Object.keys(obj.nested!) + .map(name => { + return getAllHandledReflectionObjects(obj.nested![name], objName); + }) + .reduce( + (accumulator, currentValue) => accumulator.concat(currentValue), + [] + ); + } + } + return []; +} + +function createDeserializer( + cls: Protobuf.Type, + options: Options +): Deserialize { + return function deserialize(argBuf: Buffer): object { + return cls.toObject(cls.decode(argBuf), options); + }; +} + +function createSerializer(cls: Protobuf.Type): Serialize { + return function serialize(arg: object): Buffer { + if (Array.isArray(arg)) { + throw new Error(`Failed to serialize message: expected object with ${cls.name} structure, got array instead`); + } + const message = cls.fromObject(arg); + return cls.encode(message).finish() as Buffer; + }; +} + +function createMethodDefinition( + method: Protobuf.Method, + serviceName: string, + options: Options, + fileDescriptors: Buffer[] +): MethodDefinition { + /* This is only ever called after the corresponding root.resolveAll(), so we + * can assume that the resolved request and response types are non-null */ + const requestType: Protobuf.Type = method.resolvedRequestType!; + const responseType: Protobuf.Type = method.resolvedResponseType!; + return { + path: '/' + serviceName + '/' + method.name, + requestStream: !!method.requestStream, + responseStream: !!method.responseStream, + requestSerialize: createSerializer(requestType), + requestDeserialize: createDeserializer(requestType, options), + responseSerialize: createSerializer(responseType), + responseDeserialize: createDeserializer(responseType, options), + // TODO(murgatroid99): Find a better way to handle this + originalName: camelCase(method.name), + requestType: createMessageDefinition(requestType, fileDescriptors), + responseType: createMessageDefinition(responseType, fileDescriptors), + }; +} + +function createServiceDefinition( + service: Protobuf.Service, + name: string, + options: Options, + fileDescriptors: Buffer[] +): ServiceDefinition { + const def: ServiceDefinition = {}; + for (const method of service.methodsArray) { + def[method.name] = createMethodDefinition( + method, + name, + options, + fileDescriptors + ); + } + return def; +} + +function createMessageDefinition( + message: Protobuf.Type, + fileDescriptors: Buffer[] +): MessageTypeDefinition { + const messageDescriptor: protobuf.Message< + descriptor.IDescriptorProto + > = message.toDescriptor('proto3'); + return { + format: 'Protocol Buffer 3 DescriptorProto', + type: messageDescriptor.$type.toObject( + messageDescriptor, + descriptorOptions + ), + fileDescriptorProtos: fileDescriptors, + }; +} + +function createEnumDefinition( + enumType: Protobuf.Enum, + fileDescriptors: Buffer[] +): EnumTypeDefinition { + const enumDescriptor: protobuf.Message< + descriptor.IEnumDescriptorProto + > = enumType.toDescriptor('proto3'); + return { + format: 'Protocol Buffer 3 EnumDescriptorProto', + type: enumDescriptor.$type.toObject(enumDescriptor, descriptorOptions), + fileDescriptorProtos: fileDescriptors, + }; +} + +/** + * function createDefinition(obj: Protobuf.Service, name: string, options: + * Options): ServiceDefinition; function createDefinition(obj: Protobuf.Type, + * name: string, options: Options): MessageTypeDefinition; function + * createDefinition(obj: Protobuf.Enum, name: string, options: Options): + * EnumTypeDefinition; + */ +function createDefinition( + obj: HandledReflectionObject, + name: string, + options: Options, + fileDescriptors: Buffer[] +): AnyDefinition { + if (obj instanceof Protobuf.Service) { + return createServiceDefinition(obj, name, options, fileDescriptors); + } else if (obj instanceof Protobuf.Type) { + return createMessageDefinition(obj, fileDescriptors); + } else if (obj instanceof Protobuf.Enum) { + return createEnumDefinition(obj, fileDescriptors); + } else { + throw new Error('Type mismatch in reflection object handling'); + } +} + +function createPackageDefinition( + root: Protobuf.Root, + options: Options +): PackageDefinition { + const def: PackageDefinition = {}; + root.resolveAll(); + const descriptorList: descriptor.IFileDescriptorProto[] = root.toDescriptor( + 'proto3' + ).file; + const bufferList: Buffer[] = descriptorList.map(value => + Buffer.from(descriptor.FileDescriptorProto.encode(value).finish()) + ); + for (const [name, obj] of getAllHandledReflectionObjects(root, '')) { + def[name] = createDefinition(obj, name, options, bufferList); + } + return def; +} + +function createPackageDefinitionFromDescriptorSet( + decodedDescriptorSet: DecodedDescriptorSet, + options?: Options +) { + options = options || {}; + + const root = (Protobuf.Root as Protobuf.RootConstructor).fromDescriptor( + decodedDescriptorSet + ); + root.resolveAll(); + return createPackageDefinition(root, options); +} + +/** + * Load a .proto file with the specified options. + * @param filename One or multiple file paths to load. Can be an absolute path + * or relative to an include path. + * @param options.keepCase Preserve field names. The default is to change them + * to camel case. + * @param options.longs The type that should be used to represent `long` values. + * Valid options are `Number` and `String`. Defaults to a `Long` object type + * from a library. + * @param options.enums The type that should be used to represent `enum` values. + * The only valid option is `String`. Defaults to the numeric value. + * @param options.bytes The type that should be used to represent `bytes` + * values. Valid options are `Array` and `String`. The default is to use + * `Buffer`. + * @param options.defaults Set default values on output objects. Defaults to + * `false`. + * @param options.arrays Set empty arrays for missing array values even if + * `defaults` is `false`. Defaults to `false`. + * @param options.objects Set empty objects for missing object values even if + * `defaults` is `false`. Defaults to `false`. + * @param options.oneofs Set virtual oneof properties to the present field's + * name + * @param options.json Represent Infinity and NaN as strings in float fields, + * and automatically decode google.protobuf.Any values. + * @param options.includeDirs Paths to search for imported `.proto` files. + */ +export function load( + filename: string | string[], + options?: Options +): Promise { + return loadProtosWithOptions(filename, options).then(loadedRoot => { + return createPackageDefinition(loadedRoot, options!); + }); +} + +export function loadSync( + filename: string | string[], + options?: Options +): PackageDefinition { + const loadedRoot = loadProtosWithOptionsSync(filename, options); + return createPackageDefinition(loadedRoot, options!); +} + +export function fromJSON( + json: Protobuf.INamespace, + options?: Options +): PackageDefinition { + options = options || {}; + const loadedRoot = Protobuf.Root.fromJSON(json); + loadedRoot.resolveAll(); + return createPackageDefinition(loadedRoot, options!); +} + +export function loadFileDescriptorSetFromBuffer( + descriptorSet: Buffer, + options?: Options +): PackageDefinition { + const decodedDescriptorSet = descriptor.FileDescriptorSet.decode( + descriptorSet + ) as DecodedDescriptorSet; + + return createPackageDefinitionFromDescriptorSet( + decodedDescriptorSet, + options + ); +} + +export function loadFileDescriptorSetFromObject( + descriptorSet: Parameters[0], + options?: Options +): PackageDefinition { + const decodedDescriptorSet = descriptor.FileDescriptorSet.fromObject( + descriptorSet + ) as DecodedDescriptorSet; + + return createPackageDefinitionFromDescriptorSet( + decodedDescriptorSet, + options + ); +} + +addCommonProtos(); diff --git a/packages/proto-loader/src/util.ts b/packages/proto-loader/src/util.ts new file mode 100644 index 000000000..a4a8502b6 --- /dev/null +++ b/packages/proto-loader/src/util.ts @@ -0,0 +1,113 @@ +/** + * @license + * Copyright 2018 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. + * + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as Protobuf from 'protobufjs'; + +function addIncludePathResolver(root: Protobuf.Root, includePaths: string[]) { + const originalResolvePath = root.resolvePath; + root.resolvePath = (origin: string, target: string) => { + if (path.isAbsolute(target)) { + return target; + } + for (const directory of includePaths) { + const fullPath: string = path.join(directory, target); + try { + fs.accessSync(fullPath, fs.constants.R_OK); + return fullPath; + } catch (err) { + continue; + } + } + process.emitWarning(`${target} not found in any of the include paths ${includePaths}`); + return originalResolvePath(origin, target); + }; +} + +export type Options = Protobuf.IParseOptions & + Protobuf.IConversionOptions & { + includeDirs?: string[]; + }; + +export async function loadProtosWithOptions( + filename: string | string[], + options?: Options +): Promise { + const root: Protobuf.Root = new Protobuf.Root(); + options = options || {}; + if (!!options.includeDirs) { + if (!Array.isArray(options.includeDirs)) { + return Promise.reject( + new Error('The includeDirs option must be an array') + ); + } + addIncludePathResolver(root, options.includeDirs as string[]); + } + const loadedRoot = await root.load(filename, options); + loadedRoot.resolveAll(); + return loadedRoot; +} + +export function loadProtosWithOptionsSync( + filename: string | string[], + options?: Options +): Protobuf.Root { + const root: Protobuf.Root = new Protobuf.Root(); + options = options || {}; + if (!!options.includeDirs) { + if (!Array.isArray(options.includeDirs)) { + throw new Error('The includeDirs option must be an array'); + } + addIncludePathResolver(root, options.includeDirs as string[]); + } + const loadedRoot = root.loadSync(filename, options); + loadedRoot.resolveAll(); + return loadedRoot; +} + +/** + * Load Google's well-known proto files that aren't exposed by Protobuf.js. + */ +export function addCommonProtos(): void { + // Protobuf.js exposes: any, duration, empty, field_mask, struct, timestamp, + // and wrappers. compiler/plugin is excluded in Protobuf.js and here. + + // Using constant strings for compatibility with tools like Webpack + const apiDescriptor = require('protobufjs/google/protobuf/api.json'); + const descriptorDescriptor = require('protobufjs/google/protobuf/descriptor.json'); + const sourceContextDescriptor = require('protobufjs/google/protobuf/source_context.json'); + const typeDescriptor = require('protobufjs/google/protobuf/type.json'); + + Protobuf.common( + 'api', + apiDescriptor.nested.google.nested.protobuf.nested + ); + Protobuf.common( + 'descriptor', + descriptorDescriptor.nested.google.nested.protobuf.nested + ); + Protobuf.common( + 'source_context', + sourceContextDescriptor.nested.google.nested.protobuf.nested + ); + Protobuf.common( + 'type', + typeDescriptor.nested.google.nested.protobuf.nested + ); +} \ No newline at end of file diff --git a/packages/proto-loader/test/descriptor_type_test.ts b/packages/proto-loader/test/descriptor_type_test.ts new file mode 100644 index 000000000..180681c12 --- /dev/null +++ b/packages/proto-loader/test/descriptor_type_test.ts @@ -0,0 +1,131 @@ +/* + * Copyright 2019 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. + * + */ + +import * as assert from 'assert'; +import { rpcFileDescriptorSet } from '../test_protos/rpc.desc'; +import { readFileSync } from 'fs'; + +import * as proto_loader from '../src/index'; + +// Relative path from build output directory to test_protos directory +const TEST_PROTO_DIR = `${__dirname}/../../test_protos/`; + +type TypeDefinition = + | proto_loader.EnumTypeDefinition + | proto_loader.MessageTypeDefinition; + +function isTypeObject(obj: proto_loader.AnyDefinition): obj is TypeDefinition { + return 'format' in obj; +} + +describe('Descriptor types', () => { + it('Should be output for each enum', done => { + proto_loader.load(`${TEST_PROTO_DIR}/enums.proto`).then( + packageDefinition => { + assert('Enum1' in packageDefinition); + assert(isTypeObject(packageDefinition.Enum1)); + // Need additional check because compiler doesn't understand + // asserts + if (isTypeObject(packageDefinition.Enum1)) { + const enum1Def: TypeDefinition = packageDefinition.Enum1; + assert.strictEqual( + enum1Def.format, + 'Protocol Buffer 3 EnumDescriptorProto' + ); + } + + assert('Enum2' in packageDefinition); + assert(isTypeObject(packageDefinition.Enum2)); + // Need additional check because compiler doesn't understand + // asserts + if (isTypeObject(packageDefinition.Enum2)) { + const enum2Def: TypeDefinition = packageDefinition.Enum2; + assert.strictEqual( + enum2Def.format, + 'Protocol Buffer 3 EnumDescriptorProto' + ); + } + done(); + }, + error => { + done(error); + } + ); + }); + it('Should be output for each message', done => { + proto_loader.load(`${TEST_PROTO_DIR}/messages.proto`).then( + packageDefinition => { + assert('LongValues' in packageDefinition); + assert(isTypeObject(packageDefinition.LongValues)); + if (isTypeObject(packageDefinition.LongValues)) { + const longValuesDef: TypeDefinition = packageDefinition.LongValues; + assert.strictEqual( + longValuesDef.format, + 'Protocol Buffer 3 DescriptorProto' + ); + } + + assert('SequenceValues' in packageDefinition); + assert(isTypeObject(packageDefinition.SequenceValues)); + if (isTypeObject(packageDefinition.SequenceValues)) { + const sequenceValuesDef: TypeDefinition = + packageDefinition.SequenceValues; + assert.strictEqual( + sequenceValuesDef.format, + 'Protocol Buffer 3 DescriptorProto' + ); + } + done(); + }, + error => { + done(error); + } + ); + }); + + it('Can use well known Google protos', () => { + // This will throw if the well known protos are not available. + proto_loader.loadSync(`${TEST_PROTO_DIR}/well_known.proto`); + }); + + it('Can load JSON descriptors', () => { + // This is protobuf.js JSON descriptor + // https://github.com/protobufjs/protobuf.js#using-json-descriptors + const buffer = readFileSync(`${TEST_PROTO_DIR}/rpc.proto.json`); + const json = JSON.parse(buffer.toString()); + // This will throw if the rpc descriptor JSON cannot be decoded + proto_loader.fromJSON(json); + }); + + it('Can load binary-encoded proto file descriptor sets', () => { + const buffer = readFileSync(`${TEST_PROTO_DIR}/rpc.desc.bin`); + // This will throw if the rpc descriptor cannot be decoded + proto_loader.loadFileDescriptorSetFromBuffer(buffer); + }); + + it('Can load json file descriptor sets', () => { + const buffer = readFileSync(`${TEST_PROTO_DIR}/rpc.desc.json`); + const json = JSON.parse(buffer.toString()); + // This will throw if the rpc descriptor JSON cannot be decoded + proto_loader.loadFileDescriptorSetFromObject(json); + }); + + it('Can parse plain file descriptor set objects', () => { + // This will throw if the file descriptor object cannot be parsed + proto_loader.loadFileDescriptorSetFromObject(rpcFileDescriptorSet); + }); +}); diff --git a/packages/proto-loader/test_protos/enums.proto b/packages/proto-loader/test_protos/enums.proto new file mode 100644 index 000000000..58e32532c --- /dev/null +++ b/packages/proto-loader/test_protos/enums.proto @@ -0,0 +1,27 @@ +// Copyright 2019 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"; + +enum Enum1 { + DEFAULT = 0; + VALUE1 = 1; + VALUE2 = 2; +} + +enum Enum2 { + DEFAULT = 0; + ABC = 5; + DEF = 10; +} \ No newline at end of file diff --git a/test/api/test_messages.proto b/packages/proto-loader/test_protos/messages.proto similarity index 76% rename from test/api/test_messages.proto rename to packages/proto-loader/test_protos/messages.proto index da1ef5658..706274515 100644 --- a/test/api/test_messages.proto +++ b/packages/proto-loader/test_protos/messages.proto @@ -1,4 +1,4 @@ -// Copyright 2015 gRPC authors. +// Copyright 2019 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,21 +25,4 @@ message LongValues { message SequenceValues { bytes bytes_field = 1; repeated int32 repeated_field = 2; -} - -message OneOfValues { - oneof oneof_choice { - int32 int_choice = 1; - string string_choice = 2; - } -} - -enum TestEnum { - ZERO = 0; - ONE = 1; - TWO = 2; -} - -message EnumValues { - TestEnum enum_value = 1; -} +} \ No newline at end of file diff --git a/packages/proto-loader/test_protos/rpc.desc.bin b/packages/proto-loader/test_protos/rpc.desc.bin new file mode 100644 index 000000000..9c6f12611 --- /dev/null +++ b/packages/proto-loader/test_protos/rpc.desc.bin @@ -0,0 +1,11 @@ + +˜ +test_protos/rpc.proto" + MyRequest +path ( Rpath"$ + +MyResponse +status (Rstatus20 + MyService# +MyMethod +.MyRequest .MyResponsebproto3 \ No newline at end of file diff --git a/packages/proto-loader/test_protos/rpc.desc.json b/packages/proto-loader/test_protos/rpc.desc.json new file mode 100644 index 000000000..3d608e0b4 --- /dev/null +++ b/packages/proto-loader/test_protos/rpc.desc.json @@ -0,0 +1,46 @@ +{ + "file": [ + { + "name": "test_protos/rpc.proto", + "messageType": [ + { + "name": "MyRequest", + "field": [ + { + "name": "path", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "path" + } + ] + }, + { + "name": "MyResponse", + "field": [ + { + "name": "status", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT32", + "jsonName": "status" + } + ] + } + ], + "service": [ + { + "name": "MyService", + "method": [ + { + "name": "MyMethod", + "inputType": ".MyRequest", + "outputType": ".MyResponse" + } + ] + } + ], + "syntax": "proto3" + } + ] +} diff --git a/packages/proto-loader/test_protos/rpc.desc.ts b/packages/proto-loader/test_protos/rpc.desc.ts new file mode 100644 index 000000000..7e52ea0db --- /dev/null +++ b/packages/proto-loader/test_protos/rpc.desc.ts @@ -0,0 +1,46 @@ +export const rpcFileDescriptorSet = { + "file": [ + { + "name": "test_protos/rpc.proto", + "messageType": [ + { + "name": "MyRequest", + "field": [ + { + "name": "path", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "path" + } + ] + }, + { + "name": "MyResponse", + "field": [ + { + "name": "status", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT32", + "jsonName": "status" + } + ] + } + ], + "service": [ + { + "name": "MyService", + "method": [ + { + "name": "MyMethod", + "inputType": ".MyRequest", + "outputType": ".MyResponse" + } + ] + } + ], + "syntax": "proto3" + } + ] +} diff --git a/packages/proto-loader/test_protos/rpc.proto b/packages/proto-loader/test_protos/rpc.proto new file mode 100644 index 000000000..a9841c06b --- /dev/null +++ b/packages/proto-loader/test_protos/rpc.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +service MyService { + rpc MyMethod (MyRequest) returns (MyResponse); +} + +message MyRequest { + string path = 1; +} + +message MyResponse { + int32 status = 2; +} diff --git a/packages/proto-loader/test_protos/rpc.proto.json b/packages/proto-loader/test_protos/rpc.proto.json new file mode 100644 index 000000000..5f96dba93 --- /dev/null +++ b/packages/proto-loader/test_protos/rpc.proto.json @@ -0,0 +1,1517 @@ +{ + "options": { + "java_package": "com.google.apps.jspb.proto", + "java_multiple_files": true + }, + "nested": { + "jspb": { + "nested": { + "test": { + "nested": { + "Empty": { + "fields": {} + }, + "OuterEnum": { + "values": { + "FOO": 1, + "BAR": 2 + } + }, + "EnumContainer": { + "fields": { + "outerEnum": { + "type": "OuterEnum", + "id": 1 + } + } + }, + "Simple1": { + "fields": { + "aString": { + "rule": "required", + "type": "string", + "id": 1 + }, + "aRepeatedString": { + "rule": "repeated", + "type": "string", + "id": 2 + }, + "aBoolean": { + "type": "bool", + "id": 3 + } + } + }, + "Simple2": { + "fields": { + "aString": { + "rule": "required", + "type": "string", + "id": 1 + }, + "aRepeatedString": { + "rule": "repeated", + "type": "string", + "id": 2 + } + } + }, + "SpecialCases": { + "fields": { + "normal": { + "rule": "required", + "type": "string", + "id": 1 + }, + "default": { + "rule": "required", + "type": "string", + "id": 2 + }, + "function": { + "rule": "required", + "type": "string", + "id": 3 + }, + "var": { + "rule": "required", + "type": "string", + "id": 4 + } + } + }, + "OptionalFields": { + "fields": { + "aString": { + "type": "string", + "id": 1 + }, + "aBool": { + "rule": "required", + "type": "bool", + "id": 2 + }, + "aNestedMessage": { + "type": "Nested", + "id": 3 + }, + "aRepeatedMessage": { + "rule": "repeated", + "type": "Nested", + "id": 4 + }, + "aRepeatedString": { + "rule": "repeated", + "type": "string", + "id": 5 + } + }, + "nested": { + "Nested": { + "fields": { + "anInt": { + "type": "int32", + "id": 1 + } + } + } + } + }, + "HasExtensions": { + "fields": { + "str1": { + "type": "string", + "id": 1 + }, + "str2": { + "type": "string", + "id": 2 + }, + "str3": { + "type": "string", + "id": 3 + } + }, + "extensions": [ + [ + 10, + 536870911 + ] + ] + }, + "Complex": { + "fields": { + "aString": { + "rule": "required", + "type": "string", + "id": 1 + }, + "anOutOfOrderBool": { + "rule": "required", + "type": "bool", + "id": 9 + }, + "aNestedMessage": { + "type": "Nested", + "id": 4 + }, + "aRepeatedMessage": { + "rule": "repeated", + "type": "Nested", + "id": 5 + }, + "aRepeatedString": { + "rule": "repeated", + "type": "string", + "id": 7 + } + }, + "nested": { + "Nested": { + "fields": { + "anInt": { + "rule": "required", + "type": "int32", + "id": 2 + } + } + } + } + }, + "OuterMessage": { + "fields": {}, + "nested": { + "Complex": { + "fields": { + "innerComplexField": { + "type": "int32", + "id": 1 + } + } + } + } + }, + "IsExtension": { + "fields": { + "ext1": { + "type": "string", + "id": 1 + } + }, + "nested": { + "extField": { + "type": "IsExtension", + "id": 100, + "extend": "HasExtensions" + }, + "simpleOption": { + "type": "string", + "id": 42113038, + "extend": "google.protobuf.EnumOptions" + } + } + }, + "IndirectExtension": { + "fields": {}, + "nested": { + "simple": { + "type": "Simple1", + "id": 101, + "extend": "HasExtensions" + }, + "str": { + "type": "string", + "id": 102, + "extend": "HasExtensions" + }, + "repeatedStr": { + "rule": "repeated", + "type": "string", + "id": 103, + "extend": "HasExtensions" + }, + "repeatedSimple": { + "rule": "repeated", + "type": "Simple1", + "id": 104, + "extend": "HasExtensions" + } + } + }, + "simple1": { + "type": "Simple1", + "id": 105, + "extend": "HasExtensions" + }, + "DefaultValues": { + "fields": { + "stringField": { + "type": "string", + "id": 1, + "options": { + "default": "default<>abc" + } + }, + "boolField": { + "type": "bool", + "id": 2, + "options": { + "default": true + } + }, + "intField": { + "type": "int64", + "id": 3, + "options": { + "default": 11 + } + }, + "enumField": { + "type": "Enum", + "id": 4, + "options": { + "default": "E1" + } + }, + "emptyField": { + "type": "string", + "id": 6, + "options": { + "default": "" + } + }, + "bytesField": { + "type": "bytes", + "id": 8, + "options": { + "default": "moo" + } + } + }, + "nested": { + "Enum": { + "values": { + "E1": 13, + "E2": 77 + } + } + } + }, + "FloatingPointFields": { + "fields": { + "optionalFloatField": { + "type": "float", + "id": 1 + }, + "requiredFloatField": { + "rule": "required", + "type": "float", + "id": 2 + }, + "repeatedFloatField": { + "rule": "repeated", + "type": "float", + "id": 3, + "options": { + "packed": false + } + }, + "defaultFloatField": { + "type": "float", + "id": 4, + "options": { + "default": 2 + } + }, + "optionalDoubleField": { + "type": "double", + "id": 5 + }, + "requiredDoubleField": { + "rule": "required", + "type": "double", + "id": 6 + }, + "repeatedDoubleField": { + "rule": "repeated", + "type": "double", + "id": 7, + "options": { + "packed": false + } + }, + "defaultDoubleField": { + "type": "double", + "id": 8, + "options": { + "default": 2 + } + } + } + }, + "TestClone": { + "fields": { + "str": { + "type": "string", + "id": 1 + }, + "simple1": { + "type": "Simple1", + "id": 3 + }, + "simple2": { + "rule": "repeated", + "type": "Simple1", + "id": 5 + }, + "bytesField": { + "type": "bytes", + "id": 6 + }, + "unused": { + "type": "string", + "id": 7 + } + }, + "extensions": [ + [ + 10, + 536870911 + ] + ] + }, + "CloneExtension": { + "fields": { + "ext": { + "type": "string", + "id": 2 + } + }, + "nested": { + "extField": { + "type": "CloneExtension", + "id": 100, + "extend": "TestClone" + } + } + }, + "TestGroup": { + "fields": { + "repeatedGroup": { + "rule": "repeated", + "type": "RepeatedGroup", + "id": 1 + }, + "requiredGroup": { + "rule": "required", + "type": "RequiredGroup", + "id": 2 + }, + "optionalGroup": { + "type": "OptionalGroup", + "id": 3 + }, + "id": { + "type": "string", + "id": 4 + }, + "requiredSimple": { + "rule": "required", + "type": "Simple2", + "id": 5 + }, + "optionalSimple": { + "type": "Simple2", + "id": 6 + } + }, + "nested": { + "RepeatedGroup": { + "fields": { + "id": { + "rule": "required", + "type": "string", + "id": 1 + }, + "someBool": { + "rule": "repeated", + "type": "bool", + "id": 2, + "options": { + "packed": false + } + } + }, + "group": true + }, + "RequiredGroup": { + "fields": { + "id": { + "rule": "required", + "type": "string", + "id": 1 + } + }, + "group": true + }, + "OptionalGroup": { + "fields": { + "id": { + "rule": "required", + "type": "string", + "id": 1 + } + }, + "group": true + } + } + }, + "TestGroup1": { + "fields": { + "group": { + "type": "TestGroup.RepeatedGroup", + "id": 1 + } + } + }, + "TestReservedNames": { + "fields": { + "extension": { + "type": "int32", + "id": 1 + } + }, + "extensions": [ + [ + 10, + 536870911 + ] + ] + }, + "TestReservedNamesExtension": { + "fields": {}, + "nested": { + "foo": { + "type": "int32", + "id": 10, + "extend": "TestReservedNames" + } + } + }, + "TestMessageWithOneof": { + "oneofs": { + "partialOneof": { + "oneof": [ + "pone", + "pthree" + ] + }, + "recursiveOneof": { + "oneof": [ + "rone", + "rtwo" + ] + }, + "defaultOneofA": { + "oneof": [ + "aone", + "atwo" + ] + }, + "defaultOneofB": { + "oneof": [ + "bone", + "btwo" + ] + } + }, + "fields": { + "pone": { + "type": "string", + "id": 3 + }, + "pthree": { + "type": "string", + "id": 5 + }, + "rone": { + "type": "TestMessageWithOneof", + "id": 6 + }, + "rtwo": { + "type": "string", + "id": 7 + }, + "normalField": { + "type": "bool", + "id": 8 + }, + "repeatedField": { + "rule": "repeated", + "type": "string", + "id": 9 + }, + "aone": { + "type": "int32", + "id": 10, + "options": { + "default": 1234 + } + }, + "atwo": { + "type": "int32", + "id": 11 + }, + "bone": { + "type": "int32", + "id": 12 + }, + "btwo": { + "type": "int32", + "id": 13, + "options": { + "default": 1234 + } + } + } + }, + "TestEndsWithBytes": { + "fields": { + "value": { + "type": "int32", + "id": 1 + }, + "data": { + "type": "bytes", + "id": 2 + } + } + }, + "TestMapFieldsNoBinary": { + "fields": { + "mapStringString": { + "keyType": "string", + "type": "string", + "id": 1 + }, + "mapStringInt32": { + "keyType": "string", + "type": "int32", + "id": 2 + }, + "mapStringInt64": { + "keyType": "string", + "type": "int64", + "id": 3 + }, + "mapStringBool": { + "keyType": "string", + "type": "bool", + "id": 4 + }, + "mapStringDouble": { + "keyType": "string", + "type": "double", + "id": 5 + }, + "mapStringEnum": { + "keyType": "string", + "type": "MapValueEnumNoBinary", + "id": 6 + }, + "mapStringMsg": { + "keyType": "string", + "type": "MapValueMessageNoBinary", + "id": 7 + }, + "mapInt32String": { + "keyType": "int32", + "type": "string", + "id": 8 + }, + "mapInt64String": { + "keyType": "int64", + "type": "string", + "id": 9 + }, + "mapBoolString": { + "keyType": "bool", + "type": "string", + "id": 10 + }, + "testMapFields": { + "type": "TestMapFieldsNoBinary", + "id": 11 + }, + "mapStringTestmapfields": { + "keyType": "string", + "type": "TestMapFieldsNoBinary", + "id": 12 + } + } + }, + "MapValueEnumNoBinary": { + "values": { + "MAP_VALUE_FOO_NOBINARY": 0, + "MAP_VALUE_BAR_NOBINARY": 1, + "MAP_VALUE_BAZ_NOBINARY": 2 + } + }, + "MapValueMessageNoBinary": { + "fields": { + "foo": { + "type": "int32", + "id": 1 + } + } + }, + "Deeply": { + "fields": {}, + "nested": { + "Nested": { + "fields": {}, + "nested": { + "Message": { + "fields": { + "count": { + "type": "int32", + "id": 1 + } + } + } + } + } + } + } + } + } + } + }, + "google": { + "nested": { + "protobuf": { + "options": { + "go_package": "descriptor", + "java_package": "com.google.protobuf", + "java_outer_classname": "DescriptorProtos", + "csharp_namespace": "Google.Protobuf.Reflection", + "objc_class_prefix": "GPB", + "optimize_for": "SPEED" + }, + "nested": { + "FileDescriptorSet": { + "fields": { + "file": { + "rule": "repeated", + "type": "FileDescriptorProto", + "id": 1 + } + } + }, + "FileDescriptorProto": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "package": { + "type": "string", + "id": 2 + }, + "dependency": { + "rule": "repeated", + "type": "string", + "id": 3 + }, + "publicDependency": { + "rule": "repeated", + "type": "int32", + "id": 10, + "options": { + "packed": false + } + }, + "weakDependency": { + "rule": "repeated", + "type": "int32", + "id": 11, + "options": { + "packed": false + } + }, + "messageType": { + "rule": "repeated", + "type": "DescriptorProto", + "id": 4 + }, + "enumType": { + "rule": "repeated", + "type": "EnumDescriptorProto", + "id": 5 + }, + "service": { + "rule": "repeated", + "type": "ServiceDescriptorProto", + "id": 6 + }, + "extension": { + "rule": "repeated", + "type": "FieldDescriptorProto", + "id": 7 + }, + "options": { + "type": "FileOptions", + "id": 8 + }, + "sourceCodeInfo": { + "type": "SourceCodeInfo", + "id": 9 + }, + "syntax": { + "type": "string", + "id": 12 + } + } + }, + "DescriptorProto": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "field": { + "rule": "repeated", + "type": "FieldDescriptorProto", + "id": 2 + }, + "extension": { + "rule": "repeated", + "type": "FieldDescriptorProto", + "id": 6 + }, + "nestedType": { + "rule": "repeated", + "type": "DescriptorProto", + "id": 3 + }, + "enumType": { + "rule": "repeated", + "type": "EnumDescriptorProto", + "id": 4 + }, + "extensionRange": { + "rule": "repeated", + "type": "ExtensionRange", + "id": 5 + }, + "oneofDecl": { + "rule": "repeated", + "type": "OneofDescriptorProto", + "id": 8 + }, + "options": { + "type": "MessageOptions", + "id": 7 + }, + "reservedRange": { + "rule": "repeated", + "type": "ReservedRange", + "id": 9 + }, + "reservedName": { + "rule": "repeated", + "type": "string", + "id": 10 + } + }, + "nested": { + "ExtensionRange": { + "fields": { + "start": { + "type": "int32", + "id": 1 + }, + "end": { + "type": "int32", + "id": 2 + } + } + }, + "ReservedRange": { + "fields": { + "start": { + "type": "int32", + "id": 1 + }, + "end": { + "type": "int32", + "id": 2 + } + } + } + } + }, + "FieldDescriptorProto": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "number": { + "type": "int32", + "id": 3 + }, + "label": { + "type": "Label", + "id": 4 + }, + "type": { + "type": "Type", + "id": 5 + }, + "typeName": { + "type": "string", + "id": 6 + }, + "extendee": { + "type": "string", + "id": 2 + }, + "defaultValue": { + "type": "string", + "id": 7 + }, + "oneofIndex": { + "type": "int32", + "id": 9 + }, + "jsonName": { + "type": "string", + "id": 10 + }, + "options": { + "type": "FieldOptions", + "id": 8 + } + }, + "nested": { + "Type": { + "values": { + "TYPE_DOUBLE": 1, + "TYPE_FLOAT": 2, + "TYPE_INT64": 3, + "TYPE_UINT64": 4, + "TYPE_INT32": 5, + "TYPE_FIXED64": 6, + "TYPE_FIXED32": 7, + "TYPE_BOOL": 8, + "TYPE_STRING": 9, + "TYPE_GROUP": 10, + "TYPE_MESSAGE": 11, + "TYPE_BYTES": 12, + "TYPE_UINT32": 13, + "TYPE_ENUM": 14, + "TYPE_SFIXED32": 15, + "TYPE_SFIXED64": 16, + "TYPE_SINT32": 17, + "TYPE_SINT64": 18 + } + }, + "Label": { + "values": { + "LABEL_OPTIONAL": 1, + "LABEL_REQUIRED": 2, + "LABEL_REPEATED": 3 + } + } + } + }, + "OneofDescriptorProto": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "options": { + "type": "OneofOptions", + "id": 2 + } + } + }, + "EnumDescriptorProto": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "value": { + "rule": "repeated", + "type": "EnumValueDescriptorProto", + "id": 2 + }, + "options": { + "type": "EnumOptions", + "id": 3 + } + } + }, + "EnumValueDescriptorProto": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "number": { + "type": "int32", + "id": 2 + }, + "options": { + "type": "EnumValueOptions", + "id": 3 + } + } + }, + "ServiceDescriptorProto": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "method": { + "rule": "repeated", + "type": "MethodDescriptorProto", + "id": 2 + }, + "options": { + "type": "ServiceOptions", + "id": 3 + } + } + }, + "MethodDescriptorProto": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "inputType": { + "type": "string", + "id": 2 + }, + "outputType": { + "type": "string", + "id": 3 + }, + "options": { + "type": "MethodOptions", + "id": 4 + }, + "clientStreaming": { + "type": "bool", + "id": 5, + "options": { + "default": false + } + }, + "serverStreaming": { + "type": "bool", + "id": 6, + "options": { + "default": false + } + } + } + }, + "FileOptions": { + "fields": { + "javaPackage": { + "type": "string", + "id": 1 + }, + "javaOuterClassname": { + "type": "string", + "id": 8 + }, + "javaMultipleFiles": { + "type": "bool", + "id": 10, + "options": { + "default": false + } + }, + "javaGenerateEqualsAndHash": { + "type": "bool", + "id": 20, + "options": { + "deprecated": true + } + }, + "javaStringCheckUtf8": { + "type": "bool", + "id": 27, + "options": { + "default": false + } + }, + "optimizeFor": { + "type": "OptimizeMode", + "id": 9, + "options": { + "default": "SPEED" + } + }, + "goPackage": { + "type": "string", + "id": 11 + }, + "ccGenericServices": { + "type": "bool", + "id": 16, + "options": { + "default": false + } + }, + "javaGenericServices": { + "type": "bool", + "id": 17, + "options": { + "default": false + } + }, + "pyGenericServices": { + "type": "bool", + "id": 18, + "options": { + "default": false + } + }, + "deprecated": { + "type": "bool", + "id": 23, + "options": { + "default": false + } + }, + "ccEnableArenas": { + "type": "bool", + "id": 31, + "options": { + "default": false + } + }, + "objcClassPrefix": { + "type": "string", + "id": 36 + }, + "csharpNamespace": { + "type": "string", + "id": 37 + }, + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ], + "reserved": [ + [ + 38, + 38 + ] + ], + "nested": { + "OptimizeMode": { + "values": { + "SPEED": 1, + "CODE_SIZE": 2, + "LITE_RUNTIME": 3 + } + } + } + }, + "MessageOptions": { + "fields": { + "messageSetWireFormat": { + "type": "bool", + "id": 1, + "options": { + "default": false + } + }, + "noStandardDescriptorAccessor": { + "type": "bool", + "id": 2, + "options": { + "default": false + } + }, + "deprecated": { + "type": "bool", + "id": 3, + "options": { + "default": false + } + }, + "mapEntry": { + "type": "bool", + "id": 7 + }, + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ], + "reserved": [ + [ + 8, + 8 + ] + ] + }, + "FieldOptions": { + "fields": { + "ctype": { + "type": "CType", + "id": 1, + "options": { + "default": "STRING" + } + }, + "packed": { + "type": "bool", + "id": 2 + }, + "jstype": { + "type": "JSType", + "id": 6, + "options": { + "default": "JS_NORMAL" + } + }, + "lazy": { + "type": "bool", + "id": 5, + "options": { + "default": false + } + }, + "deprecated": { + "type": "bool", + "id": 3, + "options": { + "default": false + } + }, + "weak": { + "type": "bool", + "id": 10, + "options": { + "default": false + } + }, + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ], + "reserved": [ + [ + 4, + 4 + ] + ], + "nested": { + "CType": { + "values": { + "STRING": 0, + "CORD": 1, + "STRING_PIECE": 2 + } + }, + "JSType": { + "values": { + "JS_NORMAL": 0, + "JS_STRING": 1, + "JS_NUMBER": 2 + } + } + } + }, + "OneofOptions": { + "fields": { + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ] + }, + "EnumOptions": { + "fields": { + "allowAlias": { + "type": "bool", + "id": 2 + }, + "deprecated": { + "type": "bool", + "id": 3, + "options": { + "default": false + } + }, + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ] + }, + "EnumValueOptions": { + "fields": { + "deprecated": { + "type": "bool", + "id": 1, + "options": { + "default": false + } + }, + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ] + }, + "ServiceOptions": { + "fields": { + "deprecated": { + "type": "bool", + "id": 33, + "options": { + "default": false + } + }, + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ] + }, + "MethodOptions": { + "fields": { + "deprecated": { + "type": "bool", + "id": 33, + "options": { + "default": false + } + }, + "idempotencyLevel": { + "type": "IdempotencyLevel", + "id": 34, + "options": { + "default": "IDEMPOTENCY_UNKNOWN" + } + }, + "uninterpretedOption": { + "rule": "repeated", + "type": "UninterpretedOption", + "id": 999 + } + }, + "extensions": [ + [ + 1000, + 536870911 + ] + ], + "nested": { + "IdempotencyLevel": { + "values": { + "IDEMPOTENCY_UNKNOWN": 0, + "NO_SIDE_EFFECTS": 1, + "IDEMPOTENT": 2 + } + } + } + }, + "UninterpretedOption": { + "fields": { + "name": { + "rule": "repeated", + "type": "NamePart", + "id": 2 + }, + "identifierValue": { + "type": "string", + "id": 3 + }, + "positiveIntValue": { + "type": "uint64", + "id": 4 + }, + "negativeIntValue": { + "type": "int64", + "id": 5 + }, + "doubleValue": { + "type": "double", + "id": 6 + }, + "stringValue": { + "type": "bytes", + "id": 7 + }, + "aggregateValue": { + "type": "string", + "id": 8 + } + }, + "nested": { + "NamePart": { + "fields": { + "namePart": { + "rule": "required", + "type": "string", + "id": 1 + }, + "isExtension": { + "rule": "required", + "type": "bool", + "id": 2 + } + } + } + } + }, + "SourceCodeInfo": { + "fields": { + "location": { + "rule": "repeated", + "type": "Location", + "id": 1 + } + }, + "nested": { + "Location": { + "fields": { + "path": { + "rule": "repeated", + "type": "int32", + "id": 1, + "options": { + "packed": true + } + }, + "span": { + "rule": "repeated", + "type": "int32", + "id": 2, + "options": { + "packed": true + } + }, + "leadingComments": { + "type": "string", + "id": 3 + }, + "trailingComments": { + "type": "string", + "id": 4 + }, + "leadingDetachedComments": { + "rule": "repeated", + "type": "string", + "id": 6 + } + } + } + } + }, + "GeneratedCodeInfo": { + "fields": { + "annotation": { + "rule": "repeated", + "type": "Annotation", + "id": 1 + } + }, + "nested": { + "Annotation": { + "fields": { + "path": { + "rule": "repeated", + "type": "int32", + "id": 1, + "options": { + "packed": true + } + }, + "sourceFile": { + "type": "string", + "id": 2 + }, + "begin": { + "type": "int32", + "id": 3 + }, + "end": { + "type": "int32", + "id": 4 + } + } + } + } + } + } + } + } + } + } +} diff --git a/packages/proto-loader/test_protos/well_known.proto b/packages/proto-loader/test_protos/well_known.proto new file mode 100644 index 000000000..3ff2292fc --- /dev/null +++ b/packages/proto-loader/test_protos/well_known.proto @@ -0,0 +1,25 @@ +// Copyright 2019 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"; + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.FieldOptions { + bool redact = 52000; +} + +message DescriptorHolder { + google.protobuf.DescriptorProto descriptor = 1; +} diff --git a/packages/proto-loader/tsconfig.json b/packages/proto-loader/tsconfig.json new file mode 100644 index 000000000..e46980866 --- /dev/null +++ b/packages/proto-loader/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "build", + "lib": ["es2017"], + "target": "es2017" + }, + "include": [ + "bin/**/*.ts", + "src/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/packages/proto-loader/tslint.json b/packages/proto-loader/tslint.json new file mode 100644 index 000000000..27872a139 --- /dev/null +++ b/packages/proto-loader/tslint.json @@ -0,0 +1,8 @@ +{ + "extends": "gts/tslint.json", + "linterOptions": { + "exclude": [ + "**/*.json" + ] + } +} diff --git a/run-tests.bat b/run-tests.bat index 92c2b2993..6b94b78de 100644 --- a/run-tests.bat +++ b/run-tests.bat @@ -15,32 +15,53 @@ SET ROOT=%~dp0 cd /d %~dp0 -PowerShell -Command .\install-nvm-windows.ps1 +powershell -c "Get-Host" +powershell -c "$PSVersionTable" +powershell -c "[System.Environment]::OSVersion" +powershell -c "Get-WmiObject -Class Win32_ComputerSystem" +powershell -c "(Get-WmiObject -Class Win32_ComputerSystem).SystemType" -SET NVM_HOME=%ROOT%nvm -SET NVM_SYMLINK=%ROOT%nvm\nodejs -SET PATH=%NVM_HOME%;%NVM_SYMLINK%;%PATH% +powershell -c "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; & { iwr https://raw.githubusercontent.com/grumpycoders/nvm-ps/master/nvm.ps1 | iex }" -nvm version +SET PATH=%APPDATA%\nvm-ps;%APPDATA%\nvm-ps\nodejs;%PATH% +SET JOBS=8 -nvm install 8.5.0 -nvm use 8.5.0 +call nvm version + +call nvm install 16 +call nvm use 16 + +git submodule update --init --recursive + +SET npm_config_fetch_retries=5 call npm install || goto :error -for %%v in (4.8.4 6.11.3 7.9.0 8.5.0) do ( - nvm install %%v - nvm use %%v - npm install -g npm +SET JUNIT_REPORT_STACK=1 +SET FAILED=0 + +for %%v in (14 16) do ( + call nvm install %%v + call nvm use %%v + if "%%v"=="4" ( + call npm install -g npm@5 + ) + @rem https://github.com/mapbox/node-pre-gyp/issues/362 + call npm install -g node-gyp node -e "console.log(process.versions)" - call .\node_modules\.bin\gulp clean.all || goto :error - call .\node_modules\.bin\gulp setup.windows || goto :error - call .\node_modules\.bin\gulp native.test || goto :error -) + mkdir reports\node%%v + SET JUNIT_REPORT_PATH=reports/node%%v + + node -e "process.exit(process.version.startsWith('v%%v') ? 0 : -1)" || goto :error -if %errorlevel% neq 0 exit /b %errorlevel% + call .\node_modules\.bin\gulp setup || SET FAILED=1 + call .\node_modules\.bin\gulp test || SET FAILED=1 + cmd.exe /c "SET GRPC_DNS_RESOLVER=ares& call .\node_modules\.bin\gulp nativeTestOnly" || SET FAILED=1 +) +node merge_kokoro_logs.js +if %FAILED% neq 0 exit /b 1 goto :EOF :error diff --git a/run-tests.sh b/run-tests.sh index 4e276ff4b..0adcc0f17 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -25,8 +25,10 @@ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.4/install.sh | b set -ex cd $ROOT +git submodule update --init --recursive + if [ ! -n "$node_versions" ] ; then - node_versions="4 5 6 7 8" + node_versions="14 16" fi set +ex @@ -35,12 +37,21 @@ nvm install lts/* nvm use lts/* set -ex +npm_config_fetch_retries=5 + npm install --unsafe-perm mkdir -p reports +export JOBS=8 +export JUNIT_REPORT_STACK=1 + +OS=$(uname) +ARCH=$(uname -m) # TODO(mlumish): Add electron tests +FAILED="false" + for version in ${node_versions} do # Install and setup node for the version we want. @@ -48,17 +59,24 @@ do echo "Switching to node version $version" nvm install $version nvm use $version - npm install -g npm set -ex + export JUNIT_REPORT_PATH="reports/node$version/" + + # https://github.com/mapbox/node-pre-gyp/issues/362 + npm install -g node-gyp + mkdir -p "reports/node$version" + node -e 'process.exit(process.version.startsWith("v'$version'") ? 0 : -1)' + # Install dependencies and link packages together. - ./node_modules/.bin/gulp clean.all ./node_modules/.bin/gulp setup - # Rebuild libraries and run tests. - JUNIT_REPORT_PATH="reports/node$version/" JUNIT_REPORT_STACK=1 ./node_modules/.bin/gulp native.test || FAILED="true" + # npm test calls nyc gulp test + npm test || FAILED="true" + + ./test/distrib/run-distrib-test.sh || FAILED="true" done set +ex @@ -67,7 +85,20 @@ set -ex node merge_kokoro_logs.js -if [ "$FAILED" != "" ] +if [ "$FAILED" = "true" ] then exit 1 +else + if [ "$OS" = "Linux" ] && [ "$ARCH" != "aarch64"] + then + # If we can't download the token file, just skip reporting coverage + gsutil cp gs://grpc-testing-secrets/coveralls_credentials/grpc-node.rc /tmp || exit 0 + set +x + . /tmp/grpc-node.rc + set -x + export COVERALLS_REPO_TOKEN + export COVERALLS_SERVICE_NAME=Kokoro + export COVERALLS_SERVICE_JOB_ID=$KOKORO_BUILD_ID + npm run coverage + fi fi diff --git a/packages/grpc-native-core/tools/buildgen/generate_projects.sh b/setup_interop.sh similarity index 76% rename from packages/grpc-native-core/tools/buildgen/generate_projects.sh rename to setup_interop.sh index 41a228da9..23a30e90d 100755 --- a/packages/grpc-native-core/tools/buildgen/generate_projects.sh +++ b/setup_interop.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright 2017 gRPC authors. +# Copyright 2019 gRPC authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - -set -e - -cd `dirname $0`/../.. -root=`pwd` -./deps/grpc/tools/buildgen/generate_projects.sh --base=$root --templates `find templates -type f` +npm install -g node-gyp gulp +npm install +gulp setup diff --git a/setup_interop_purejs.sh b/setup_interop_purejs.sh new file mode 100755 index 000000000..5b81c346e --- /dev/null +++ b/setup_interop_purejs.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# Copyright 2019 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. + +npm install -g gulp +npm install +gulp setupPureJSInterop diff --git a/test/aarch64/prepare_qemu.sh b/test/aarch64/prepare_qemu.sh new file mode 100755 index 000000000..f61320222 --- /dev/null +++ b/test/aarch64/prepare_qemu.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Setup and configure qemu userspace emulator on kokoro worker so that we can seamlessly emulate processes running +# inside docker containers. + +set -ex + +# show pre-existing qemu registration +cat /proc/sys/fs/binfmt_misc/qemu-aarch64 + +# Kokoro ubuntu1604 workers have already qemu-user and qemu-user-static packages installed, but it's and old version that: +# * prints warning about some syscalls (e.g "qemu: Unsupported syscall: 278") +# * doesn't register with binfmt_misc with the persistent ("F") flag we need (see below) +# +# To overcome the above limitations, we use the https://github.com/multiarch/qemu-user-static +# docker image to provide a new enough version of qemu-user-static and register it with +# the desired binfmt_misc flags. The most important flag we need is "F" (set by "--persistent yes"), +# which allows the qemu-aarch64-static binary to be loaded eagerly at the time of registration with binfmt_misc. +# That way, we can emulate aarch64 binaries running inside docker containers transparently, without needing the emulator +# binary to be accessible from the docker image we're emulating. +# Note that on newer distributions (such as glinux), simply "apt install qemu-user-static" is sufficient +# to install qemu-user-static with the right flags. +docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset --credential yes --persistent yes + +# Print current qemu reqistration to make sure everything is setup correctly. +cat /proc/sys/fs/binfmt_misc/qemu-aarch64 diff --git a/test/any_grpc.js b/test/any_grpc.js new file mode 100644 index 000000000..2f161ff7a --- /dev/null +++ b/test/any_grpc.js @@ -0,0 +1,47 @@ +/* + * Copyright 2019 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. + * + */ + +// TODO: Instead of attempting to expose both implementations of gRPC in +// a single object, the tests should be re-written in a way that makes it clear +// that two separate implementations are being tested against one another. + +const _ = require('lodash'); + +function getImplementation(globalField) { + const impl = global[globalField]; + + if (impl === 'js') { + return require(`../packages/grpc-${impl}`); + } else if (impl === 'native') { + return require('grpc'); + } + + throw new Error([ + `Invalid value for global.${globalField}: ${global.globalField}.`, + 'If running from the command line, please --require a fixture first.' + ].join(' ')); +} + +const clientImpl = getImplementation('_client_implementation'); +const serverImpl = getImplementation('_server_implementation'); + +module.exports = { + client: clientImpl, + server: serverImpl, + clientName: global._client_implementation, + serverName: global._server_implementation +}; diff --git a/test/api/async_test.js b/test/api/async_test.js deleted file mode 100644 index e899da56b..000000000 --- a/test/api/async_test.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); - -var grpc = require('grpc'); -var math = grpc.load( - __dirname + '/../../packages/grpc-native-core/deps/grpc/src/proto/math/math.proto').math; - - -/** - * Client to use to make requests to a running server. - */ -var math_client; - -/** - * Server to test against - */ -var getServer = require('./math/math_server.js'); - -var server = getServer(); - -describe('Async functionality', function() { - before(function(done) { - var port_num = server.bind('0.0.0.0:0', - grpc.ServerCredentials.createInsecure()); - server.start(); - math_client = new math.Math('localhost:' + port_num, - grpc.credentials.createInsecure()); - done(); - }); - after(function() { - grpc.closeClient(math_client); - server.forceShutdown(); - }); - it('should not hang', function(done) { - var chunkCount=0; - var call = math_client.sum(function handleSumResult(err, value) { - assert.ifError(err); - assert.equal(value.num, chunkCount); - }); - - var path = require('path'); - var fs = require('fs'); - var fileToRead = path.join(__dirname, 'numbers.txt'); - var readStream = fs.createReadStream(fileToRead); - - readStream.once('readable', function () { - readStream.on('data', function (chunk) { - call.write({'num': 1}); - chunkCount += 1; - }); - - readStream.on('end', function () { - call.end(); - }); - - readStream.on('error', function (error) { - }); - }); - - call.on('status', function checkStatus(status) { - assert.strictEqual(status.code, grpc.status.OK); - done(); - }); - }); -}); diff --git a/test/api/client_interceptors_test.js b/test/api/client_interceptors_test.js new file mode 100644 index 000000000..74a7adc47 --- /dev/null +++ b/test/api/client_interceptors_test.js @@ -0,0 +1,1825 @@ +/** + * @license + * Copyright 2018 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. + * + */ + +'use strict'; + +const options = { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true +}; +var _ = require('lodash'); +var assert = require('assert'); +const anyGrpc = require('../any_grpc'); +const clientGrpc = anyGrpc.client +const serverGrpc = anyGrpc.server; +const protoLoader = require('../../packages/proto-loader'); + +var insecureCreds = clientGrpc.credentials.createInsecure(); + +const echoProtoDef = protoLoader.loadSync(__dirname + '/../proto/echo_service.proto', options); +const EchoClient = clientGrpc.loadPackageDefinition(echoProtoDef).EchoService; +const echo_service = echoProtoDef.EchoService; + +var StatusBuilder = clientGrpc.StatusBuilder; +var ListenerBuilder = clientGrpc.ListenerBuilder; +var InterceptingCall = clientGrpc.InterceptingCall; +var RequesterBuilder = clientGrpc.RequesterBuilder; +const Metadata = clientGrpc.Metadata; + +var CallRegistry = function(done, expectation, is_ordered, is_verbose) { + this.call_map = {}; + this.call_array = []; + this.done = done; + this.expectation = expectation; + this.expectation_is_array = Array.isArray(this.expectation); + this.is_ordered = is_ordered; + this.is_verbose = is_verbose; + if (is_verbose) { + console.log('Expectation: ', expectation); + } +}; + +CallRegistry.prototype.addCall = function(call_name) { + if (this.expectation_is_array) { + this.call_array.push(call_name); + if (this.is_verbose) { + console.log(this.call_array); + } + } else { + if (!this.call_map[call_name]) { + this.call_map[call_name] = 0; + } + this.call_map[call_name]++; + if (this.is_verbose) { + console.log(this.call_map); + } + } + this.maybeCallDone(); +}; + +CallRegistry.prototype.maybeCallDone = function() { + if (this.expectation_is_array) { + if (this.is_ordered) { + if (this.expectation && _.isEqual(this.expectation, this.call_array)) { + this.done(); + } + } else { + var intersection = _.intersectionWith(this.expectation, this.call_array, + _.isEqual); + if (intersection.length === this.expectation.length) { + this.done(); + } + } + } else if (this.expectation && _.isEqual(this.expectation, this.call_map)) { + this.done(); + } +}; + +describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, function() { + describe('Client interceptors', function() { + var echo_server; + var echo_port; + var client; + + function startServer(done) { + echo_server = new serverGrpc.Server(); + echo_server.addService(echo_service, { + echo: function(call, callback) { + call.sendMetadata(call.metadata); + if (call.request.value === 'error') { + var status = { + code: 2, + message: 'test status message' + }; + status.metadata = call.metadata; + callback(status, null); + return; + } + callback(null, call.request); + }, + echoClientStream: function(call, callback){ + call.sendMetadata(call.metadata); + var payload; + var err = null; + call.on('data', function(data) { + if (data.value === 'error') { + err = { + code: 2, + message: 'test status message' + }; + err.metadata = call.metadata; + return; + } + payload = data; + }); + call.on('end', function() { + callback(err, payload, call.metadata); + }); + }, + echoServerStream: function(call) { + call.sendMetadata(call.metadata); + if (call.request.value === 'error') { + var status = { + code: 2, + message: 'test status message' + }; + status.metadata = call.metadata; + call.emit('error', status); + return; + } + call.write(call.request); + call.end(call.metadata); + }, + echoBidiStream: function(call) { + call.sendMetadata(call.metadata); + call.on('data', function(data) { + if (data.value === 'error') { + var status = { + code: 2, + message: 'test status message' + }; + call.emit('error', status); + return; + } + call.write(data); + }); + call.on('end', function() { + call.end(call.metadata); + }); + } + }); + var server_credentials = serverGrpc.ServerCredentials.createInsecure(); + echo_server.bindAsync('localhost:0', server_credentials, (error, port) => { + assert.ifError(error); + echo_port = port; + echo_server.start(); + done(); + }); + } + + function stopServer() { + echo_server.forceShutdown(); + } + + function resetClient() { + client = new EchoClient('localhost:' + echo_port, insecureCreds); + } + + before(function(done) { + startServer(done); + }); + beforeEach(function() { + resetClient(); + }); + after(function() { + stopServer(); + }); + + describe('execute downstream interceptors when a new call is made outbound', + function() { + var registry; + var options; + before(function() { + var interceptor_a = function (options, nextCall) { + var stored_listener; + var stored_metadata; + options.call_number = 1; + registry.addCall('construct a ' + options.call_number); + return new InterceptingCall(nextCall(options), { + start: function (metadata, listener, next) { + registry.addCall('start a ' + options.call_number); + stored_listener = listener; + stored_metadata = metadata; + next(metadata, listener); + }, + sendMessage: function (message, next) { + registry.addCall('send a ' + options.call_number); + var options2 = _.clone(options); + options2.call_number = 2; + var second_call = nextCall(options2); + second_call.start(stored_metadata); + second_call.sendMessage(message); + second_call.halfClose(); + next(message); + }, + halfClose: function (next) { + registry.addCall('close a ' + options.call_number); + next(); + } + }); + }; + + var interceptor_b = function (options, nextCall) { + registry.addCall('construct b ' + options.call_number); + return new InterceptingCall(nextCall(options), { + start: function (metadata, listener, next) { + registry.addCall('start b ' + options.call_number); + next(metadata, listener); + }, + sendMessage: function (message, next) { + registry.addCall('send b ' + options.call_number); + next(message); + }, + halfClose: function (next) { + registry.addCall('close b ' + options.call_number); + next(); + } + }); + }; + options = { + interceptors: [interceptor_a, interceptor_b] + }; + }); + var expected_calls = [ + 'construct a 1', + 'construct b 1', + 'start a 1', + 'start b 1', + 'send a 1', + 'construct b 2', + 'start b 2', + 'send b 2', + 'close b 2', + 'send b 1', + 'close a 1', + 'close b 1', + 'response' + ]; + + it('with unary call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var message = {}; + message.value = 'foo'; + client.echo(message, options, function(err, response){ + if (!err) { + registry.addCall('response'); + } + }); + }); + it('with client streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, false); + var message = {}; + message.value = 'foo'; + var stream = client.echoClientStream(options, function(err, response) { + if (!err) { + registry.addCall('response'); + } + }); + stream.write(message); + stream.end(); + }); + it('with server streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var message = {}; + message.value = 'foo'; + var stream = client.echoServerStream(message, options); + stream.on('data', function(data) { + registry.addCall('response'); + }); + }); + it('with bidi streaming call', function(done) { + registry = new CallRegistry( done, expected_calls, true); + var message = {}; + message.value = 'foo'; + var stream = client.echoBidiStream(options); + stream.on('data', function(data) { + registry.addCall('response'); + }); + stream.write(message); + stream.end(); + }); + }); + + + describe('execute downstream interceptors when a new call is made inbound', + function() { + var registry; + var options; + before(function() { + var interceptor_a = function (options, nextCall) { + return new InterceptingCall(nextCall(options), { + start: function (metadata, listener, next) { + next(metadata, { + onReceiveMetadata: function (metadata, next) { }, + onReceiveMessage: function (message, next) { + registry.addCall('interceptor_a'); + var second_call = nextCall(options); + second_call.start(metadata, listener); + second_call.sendMessage(message); + second_call.halfClose(); + }, + onReceiveStatus: function (status, next) { } + }); + } + }); + }; + + var interceptor_b = function (options, nextCall) { + return new InterceptingCall(nextCall(options), { + start: function (metadata, listener, next) { + next(metadata, { + onReceiveMessage: function (message, next) { + registry.addCall('interceptor_b'); + next(message); + } + }); + } + }); + }; + + options = { + interceptors: [interceptor_a, interceptor_b] + }; + + }); + var expected_calls = ['interceptor_b', 'interceptor_a', + 'interceptor_b', 'response']; + it('with unary call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var message = {}; + message.value = 'foo'; + client.echo(message, options, function(err) { + assert.ifError(err); + registry.addCall('response'); + }); + }); + it('with client streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var message = {}; + message.value = 'foo'; + var stream = client.echoClientStream(options, function(err, response) { + assert.ifError(err); + registry.addCall('response'); + }); + stream.write(message); + stream.end(); + }); + }); + + it('will delay operations and short circuit unary requests', function(done) { + var registry = new CallRegistry(done, ['foo_miss', 'foo_hit', 'bar_miss', + 'foo_hit_done', 'foo_miss_done', 'bar_miss_done']); + var cache = {}; + var _getCachedResponse = function(value) { + return cache[value]; + }; + var _store = function(key, value) { + cache[key] = value; + }; + + var interceptor = function(options, nextCall) { + var savedMetadata; + var startNext; + var storedListener; + var storedMessage; + var messageNext; + var requester = (new RequesterBuilder()) + .withStart(function(metadata, listener, next) { + savedMetadata = metadata; + storedListener = listener; + startNext = next; + }) + .withSendMessage(function(message, next) { + storedMessage = message; + messageNext = next; + }) + .withHalfClose(function(next) { + var cachedValue = _getCachedResponse(storedMessage.value); + if (cachedValue) { + var cachedMessage = {}; + cachedMessage.value = cachedValue; + registry.addCall(storedMessage.value + '_hit'); + storedListener.onReceiveMetadata(new Metadata()); + storedListener.onReceiveMessage(cachedMessage); + storedListener.onReceiveStatus( + (new StatusBuilder()).withCode(clientGrpc.status.OK).build()); + } else { + registry.addCall(storedMessage.value + '_miss'); + var newListener = (new ListenerBuilder()).withOnReceiveMessage( + function(message, next) { + _store(storedMessage.value, message.value); + next(message); + }).build(); + startNext(savedMetadata, newListener); + messageNext(storedMessage); + next(); + } + }) + .withCancel(function(message, next) { + next(); + }).build(); + + return new InterceptingCall(nextCall(options), requester); + }; + + var options = { + interceptors: [interceptor] + }; + + var foo_message = {}; + foo_message.value = 'foo'; + client.echo(foo_message, options, function(err, response){ + assert.equal(response.value, 'foo'); + registry.addCall('foo_miss_done'); + client.echo(foo_message, options, function(err, response){ + assert.equal(response.value, 'foo'); + registry.addCall('foo_hit_done'); + }); + }); + + var bar_message = {}; + bar_message.value = 'bar'; + client.echo(bar_message, options, function(err, response) { + assert.equal(response.value, 'bar'); + registry.addCall('bar_miss_done'); + }); + }); + + it('can retry failed messages and handle eventual success', function(done) { + var registry = new CallRegistry(done, + ['retry_foo_1', 'retry_foo_2', 'retry_foo_3', 'foo_result', + 'retry_bar_1', 'bar_result']); + var maxRetries = 3; + var retry_interceptor = function(options, nextCall) { + var savedMetadata; + var savedSendMessage; + var savedReceiveMessage; + var savedMessageNext; + var requester = (new RequesterBuilder()) + .withStart(function(metadata, listener, next) { + savedMetadata = metadata; + var new_listener = (new ListenerBuilder()) + .withOnReceiveMessage(function(message, next) { + savedReceiveMessage = message; + savedMessageNext = next; + }) + .withOnReceiveStatus(function(status, next) { + var retries = 0; + var retry = function(message, metadata) { + retries++; + var newCall = nextCall(options); + var receivedMessage; + newCall.start(metadata, { + onReceiveMessage: function(message) { + receivedMessage = message; + }, + onReceiveStatus: function(status) { + registry.addCall('retry_' + savedMetadata.get('name') + + '_' + retries); + if (status.code !== clientGrpc.status.OK) { + if (retries <= maxRetries) { + retry(message, metadata); + } else { + savedMessageNext(receivedMessage); + next(status); + } + } else { + registry.addCall('success_call'); + var new_status = (new StatusBuilder()) + .withCode(clientGrpc.status.OK).build(); + savedMessageNext(receivedMessage); + next(new_status); + } + } + }); + newCall.sendMessage(message); + newCall.halfClose(); + }; + if (status.code !== clientGrpc.status.OK) { + // Change the message we're sending only for test purposes + // so the server will respond without error + var newMessage = (savedMetadata.get('name')[0] === 'bar') ? + {value: 'bar'} : savedSendMessage; + retry(newMessage, savedMetadata); + } else { + savedMessageNext(savedReceiveMessage); + next(status); + } + } + ).build(); + next(metadata, new_listener); + }) + .withSendMessage(function(message, next) { + savedSendMessage = message; + next(message); + }).build(); + return new InterceptingCall(nextCall(options), requester); + }; + + var options = { + interceptors: [retry_interceptor] + }; + + // Make a call which the server will return a non-OK status for + var foo_message = {value: 'error'}; + var foo_metadata = new Metadata(); + foo_metadata.set('name', 'foo'); + client.echo(foo_message, foo_metadata, options, function(err, response) { + assert.strictEqual(err.code, 2); + registry.addCall('foo_result'); + }); + + // Make a call which will fail the first time and succeed on the first + // retry + var bar_message = {value: 'error'}; + var bar_metadata = new Metadata(); + bar_metadata.set('name', 'bar'); + client.echo(bar_message, bar_metadata, options, function(err, response) { + assert.strictEqual(response.value, 'bar'); + registry.addCall('bar_result'); + }); + }); + + it('can retry and preserve interceptor order on success', function(done) { + var registry = new CallRegistry(done, + ['interceptor_c', 'retry_interceptor', 'fail_call', 'interceptor_c', + 'success_call', 'interceptor_a', 'result'], true); + var interceptor_a = function(options, nextCall) { + var requester = (new RequesterBuilder()) + .withStart(function(metadata, listener, next) { + var new_listener = (new ListenerBuilder()) + .withOnReceiveMessage(function(message, next) { + registry.addCall('interceptor_a'); + next(message); + }).build(); + next(metadata, new_listener); + }).build(); + return new InterceptingCall(nextCall(options), requester); + }; + + var retry_interceptor = function(options, nextCall) { + var savedMetadata; + var savedMessage; + var savedMessageNext; + var sendMessageNext; + var originalMessage; + var startNext; + var originalListener; + var requester = (new RequesterBuilder()) + .withStart(function(metadata, listener, next) { + startNext = next; + savedMetadata = metadata; + originalListener = listener; + var new_listener = (new ListenerBuilder()) + .withOnReceiveMessage(function(message, next) { + savedMessage = message; + savedMessageNext = next; + }) + .withOnReceiveStatus(function(status, next) { + var retries = 0; + var maxRetries = 1; + var receivedMessage; + var retry = function(message, metadata) { + retries++; + var new_call = nextCall(options); + new_call.start(metadata, { + onReceiveMessage: function(message) { + receivedMessage = message; + }, + onReceiveStatus: function(status) { + if (status.code !== clientGrpc.status.OK) { + if (retries <= maxRetries) { + retry(message, metadata); + } else { + savedMessageNext(receivedMessage); + next(status); + } + } else { + registry.addCall('success_call'); + var new_status = (new StatusBuilder()) + .withCode(clientGrpc.status.OK).build(); + savedMessageNext(receivedMessage); + next(new_status); + } + } + }); + new_call.sendMessage(message); + new_call.halfClose(); + }; + registry.addCall('retry_interceptor'); + if (status.code !== clientGrpc.status.OK) { + registry.addCall('fail_call'); + var newMessage = {value: 'foo'}; + retry(newMessage, savedMetadata); + } else { + savedMessageNext(savedMessage); + next(status); + } + }).build(); + next(metadata, new_listener); + }) + .withSendMessage(function(message, next) { + sendMessageNext = next; + originalMessage = message; + next(message); + }) + .build(); + return new InterceptingCall(nextCall(options), requester); + }; + + var interceptor_c = function(options, nextCall) { + var requester = (new RequesterBuilder()) + .withStart(function(metadata, listener, next) { + var new_listener = (new ListenerBuilder()) + .withOnReceiveMessage(function(message, next) { + registry.addCall('interceptor_c'); + next(message); + }).build(); + next(metadata, new_listener); + }).build(); + return new InterceptingCall(nextCall(options), requester); + }; + + var options = { + interceptors: [interceptor_a, retry_interceptor, interceptor_c] + }; + + var message = {value: 'error'}; + client.echo(message, options, function(err, response) { + assert.ifError(err); + assert.strictEqual(response.value, 'foo'); + registry.addCall('result'); + }); + }); + + describe('handle interceptor errors', function () { + var options; + before(function () { + var foo_interceptor = function (options, nextCall) { + var savedListener; + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + savedListener = listener; + next(metadata, listener); + }) + .withSendMessage(function (message, next) { + savedListener.onReceiveMetadata(new Metadata()); + savedListener.onReceiveMessage({ value: 'failed' }); + var error_status = (new StatusBuilder()) + .withCode(16) + .withDetails('Error in foo interceptor') + .build(); + savedListener.onReceiveStatus(error_status); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + options = { interceptors: [foo_interceptor] }; + }); + it('with unary call', function(done) { + var message = {}; + client.echo(message, options, function(err, response) { + assert.strictEqual(err.code, 16); + assert.strictEqual(err.message, + '16 UNAUTHENTICATED: Error in foo interceptor'); + done(); + }); + }); + }); + + describe('implement fallbacks for streaming RPCs', function() { + + var options; + before(function () { + var fallback_response = { value: 'fallback' }; + var interceptor = function (options, nextCall) { + var savedMessage; + var savedMessageNext; + var requester = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + var new_listener = (new ListenerBuilder()) + .withOnReceiveMessage(function (message, next) { + savedMessage = message; + savedMessageNext = next; + }) + .withOnReceiveStatus(function (status, next) { + if (status.code !== clientGrpc.status.OK) { + savedMessageNext(fallback_response); + next((new StatusBuilder()).withCode(clientGrpc.status.OK).build()); + } else { + savedMessageNext(savedMessage); + next(status); + } + }).build(); + next(metadata, new_listener); + }).build(); + return new InterceptingCall(nextCall(options), requester); + }; + options = { + interceptors: [interceptor] + }; + }); + it('with client streaming call', function (done) { + var registry = new CallRegistry(done, ['foo_result', 'fallback_result']); + var stream = client.echoClientStream(options, function (err, response) { + assert.ifError(err); + assert.strictEqual(response.value, 'foo'); + registry.addCall('foo_result'); + }); + stream.write({ value: 'foo' }); + stream.end(); + + stream = client.echoClientStream(options, function(err, response) { + assert.ifError(err); + assert.strictEqual(response.value, 'fallback'); + registry.addCall('fallback_result'); + }); + stream.write({value: 'error'}); + stream.end(); + }); + }); + + describe('allows the call options to be modified for downstream interceptors', + function() { + var done; + var options; + var method_name; + var method_path_last; + before(function() { + var interceptor_a = function (options, nextCall) { + options.deadline = 10; + return new InterceptingCall(nextCall(options)); + }; + var interceptor_b = function (options, nextCall) { + assert.equal(options.method_definition.path, '/EchoService/' + + method_path_last); + assert.equal(options.deadline, 10); + done(); + return new InterceptingCall(nextCall(options)); + }; + + options = { + interceptors: [interceptor_a, interceptor_b], + deadline: 100 + }; + }); + + it('with unary call', function(cb) { + done = cb; + var metadata = new Metadata(); + var message = {}; + method_name = 'echo'; + method_path_last = 'Echo'; + + client.echo(message, metadata, options, function(){}); + }); + + it('with client streaming call', function(cb) { + done = cb; + var metadata = new Metadata(); + method_name = 'echoClientStream'; + method_path_last = 'EchoClientStream'; + + client.echoClientStream(metadata, options, function() {}); + }); + + it('with server streaming call', function(cb) { + done = cb; + var metadata = new Metadata(); + var message = {}; + method_name = 'echoServerStream'; + method_path_last = 'EchoServerStream'; + + const stream = client.echoServerStream(message, metadata, options); + stream.on('error', () => {}); + }); + + it('with bidi streaming call', function(cb) { + done = cb; + var metadata = new Metadata(); + method_name = 'echoBidiStream'; + method_path_last = 'EchoBidiStream'; + + const stream = client.echoBidiStream(metadata, options); + stream.on('error', () => {}); + }); + }); + + describe('pass accurate MethodDefinitions', function() { + var registry; + var initial_value = 'broken'; + var expected_value = 'working'; + var options; + before(function() { + var interceptor = function (options, nextCall) { + registry.addCall({ + path: options.method_definition.path, + requestStream: options.method_definition.requestStream, + responseStream: options.method_definition.responseStream + }); + var outbound = (new RequesterBuilder()) + .withSendMessage(function (message, next) { + message.value = expected_value; + next(message); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + options = { interceptors: [interceptor] }; + }); + + it('with unary call', function(done) { + var unary_definition = { + path: '/EchoService/Echo', + requestStream: false, + responseStream: false + }; + registry = new CallRegistry(done, [ + unary_definition, + 'result_unary' + ]); + + var metadata = new Metadata(); + + var message = {value: initial_value}; + + client.echo(message, metadata, options, function(err, response){ + assert.equal(response.value, expected_value); + registry.addCall('result_unary'); + }); + + }); + it('with client streaming call', function(done) { + var client_stream_definition = { + path: '/EchoService/EchoClientStream', + requestStream: true, + responseStream: false + }; + registry = new CallRegistry(done, [ + client_stream_definition, + 'result_client_stream' + ]); + var metadata = new Metadata(); + var message = {value: initial_value}; + var client_stream = client.echoClientStream(metadata, options, + function(err, response) { + assert.strictEqual(response.value, expected_value); + registry.addCall('result_client_stream'); + }); + client_stream.write(message); + client_stream.end(); + + }); + it('with server streaming call', function(done) { + var server_stream_definition = { + path: '/EchoService/EchoServerStream', + responseStream: true, + requestStream: false, + }; + registry = new CallRegistry(done, [ + server_stream_definition, + 'result_server_stream' + ]); + + var metadata = new Metadata(); + var message = {value: initial_value}; + var server_stream = client.echoServerStream(message, metadata, options); + server_stream.on('data', function(data) { + assert.strictEqual(data.value, expected_value); + registry.addCall('result_server_stream'); + }); + + }); + it('with bidi streaming call', function(done) { + var bidi_stream_definition = { + path: '/EchoService/EchoBidiStream', + requestStream: true, + responseStream: true + }; + registry = new CallRegistry(done, [ + bidi_stream_definition, + 'result_bidi_stream' + ]); + + var metadata = new Metadata(); + var message = {value: initial_value}; + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('data', function(data) { + assert.strictEqual(data.value, expected_value); + registry.addCall('result_bidi_stream'); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + + it('uses interceptors passed to the client constructor', function(done) { + var registry = new CallRegistry(done, { + 'constructor_interceptor_a_echo': 1, + 'constructor_interceptor_b_echoServerStream': 1, + 'invocation_interceptor': 1, + 'result_unary': 1, + 'result_stream': 1, + 'result_invocation': 1, + 'status_stream': 1 + }); + + var constructor_interceptor_a = function(options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function(metadata, listener, next) { + registry.addCall('constructor_interceptor_a_echo'); + next(metadata, listener); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + var constructor_interceptor_b = function(options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function(metadata, listener, next) { + registry.addCall('constructor_interceptor_b_echoServerStream'); + next(metadata, listener); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + var invocation_interceptor = function(options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function(metadata, listener, next) { + registry.addCall('invocation_interceptor'); + next(metadata, listener); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + + var interceptor_providers = [ + function(method_definition) { + if (!method_definition.requestStream && + !method_definition.responseStream) { + return constructor_interceptor_a; + } + }, + function(method_definition) { + if (!method_definition.requestStream && + method_definition.responseStream) { + return constructor_interceptor_b; + } + } + ]; + var constructor_options = { + interceptor_providers: interceptor_providers + }; + var int_client = new EchoClient('localhost:' + echo_port, insecureCreds, + constructor_options); + var message = {}; + int_client.echo(message, function(error, response) { + assert.ifError(error); + registry.addCall('result_unary'); + }); + var stream = int_client.echoServerStream(message); + stream.on('data', function() { + registry.addCall('result_stream'); + }); + stream.on('status', (status) => { + registry.addCall('status_stream'); + }); + stream.on('error', (error) => { + assert.ifError(error); + }); + + var options = { interceptors: [invocation_interceptor] }; + int_client.echo(message, options, function() { + registry.addCall('result_invocation'); + }); + }); + + it('will reject conflicting interceptor options at invocation', + function(done) { + const interceptor = (options, nextCall) => { + new InterceptingCall(nextCall(options)); + }; + const interceptorProvider = methodDefinition => interceptor + try { + client.echo('message', { + interceptors: [interceptor], + interceptor_providers: [interceptorProvider] + }, function () {}); + } catch (e) { + assert.equal(e.name, 'InterceptorConfigurationError'); + done(); + } + }); + + it('will resolve interceptor providers at invocation', function(done) { + var constructor_interceptor = function(options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function() { + assert(false); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + var invocation_interceptor = function(options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function() { + done(); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + var constructor_interceptor_providers = [ + function() { + return constructor_interceptor; + } + ]; + var invocation_interceptor_providers = [ + function() { + return invocation_interceptor; + } + ]; + var constructor_options = { + interceptor_providers: constructor_interceptor_providers + }; + var int_client = new EchoClient('localhost:' + echo_port, insecureCreds, + constructor_options); + var message = {}; + var options = { interceptor_providers: invocation_interceptor_providers }; + int_client.echo(message, options, function() {}); + }); + + describe('trigger a stack of interceptors in nested order', function() { + var registry; + var expected_calls = ['constructA', 'constructB', 'outboundA', 'outboundB', + 'inboundB', 'inboundA', 'callDone']; + var options; + before(function() { + var interceptor_a = function (options, nextCall) { + registry.addCall('constructA'); + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + registry.addCall('outboundA'); + var new_listener = (new ListenerBuilder()).withOnReceiveMessage( + function (message, next) { + registry.addCall('inboundA'); + next(message); + }).build(); + next(metadata, new_listener); + }).build(); + return new clientGrpc.InterceptingCall(nextCall(options), + outbound); + }; + var interceptor_b = function (options, nextCall) { + registry.addCall('constructB'); + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + registry.addCall('outboundB'); + var new_listener = (new ListenerBuilder()).withOnReceiveMessage( + function (message, next) { + registry.addCall('inboundB'); + next(message); + }).build(); + next(metadata, new_listener); + }).build(); + return new InterceptingCall(nextCall(options), + outbound); + }; + options = { interceptors: [interceptor_a, interceptor_b] }; + }); + var metadata = new Metadata(); + var message = {}; + + it('with unary call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + client.echo(message, metadata, options, function(error, response){ + assert.ifError(error); + registry.addCall('callDone'); + }); + }); + + it('with client streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var client_stream = client.echoClientStream(metadata, options, function(error, response) { + assert.ifError(error); + registry.addCall('callDone'); + }); + client_stream.write(message); + client_stream.end(); + }); + + it('with server streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var stream = client.echoServerStream(message, metadata, options); + stream.on('data', function() {}); + stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('callDone'); + }); + stream.on('error', (error) => { + assert.ifError(error); + }); + }); + + it('with bidi streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('data', function(){}); + bidi_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('callDone'); + }); + bidi_stream.on('error', (error) => { + assert.ifError(error); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + + describe('trigger interceptors horizontally', function() { + var expected_calls = [ + 'interceptor_a_start', + 'interceptor_b_start', + 'interceptor_a_send', + 'interceptor_b_send', + 'call_end' + ]; + var registry; + var options; + var metadata = new Metadata(); + var message = {}; + + before(function() { + var interceptor_a = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + registry.addCall('interceptor_a_start'); + next(metadata, listener); + }) + .withSendMessage(function (message, next) { + registry.addCall('interceptor_a_send'); + next(message); + }).build(); + return new InterceptingCall(nextCall(options), + outbound); + }; + var interceptor_b = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + registry.addCall('interceptor_b_start'); + next(metadata, listener); + }) + .withSendMessage(function (message, next) { + registry.addCall('interceptor_b_send'); + next(message); + }).build(); + return new InterceptingCall(nextCall(options), + outbound); + }; + options = { interceptors: [interceptor_a, interceptor_b] }; + }); + + it('with unary call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + client.echo(message, metadata, options, function(error, response){ + assert.ifError(error); + registry.addCall('call_end'); + }); + }); + + it('with client streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var client_stream = client.echoClientStream(metadata, options, function(error, response) { + assert.ifError(error); + registry.addCall('call_end'); + }); + client_stream.write(message); + client_stream.end(); + }); + + it('with server streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var stream = client.echoServerStream(message, metadata, options); + stream.on('data', function() {}); + stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('call_end'); + }); + stream.on('error', (error) => { + assert.ifError(error); + }); + }); + + it('with bidi streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('data', function(){}); + bidi_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('call_end'); + }); + bidi_stream.on('error', (error) => { + assert.ifError(error); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + + describe('trigger when sending metadata', function() { + var registry; + + var message = {}; + var key_names = ['original', 'foo', 'bar']; + var keys = { + original: 'originalkey', + foo: 'fookey', + bar: 'barkey' + }; + var values = { + original: 'originalvalue', + foo: 'foovalue', + bar: 'barvalue' + }; + var expected_calls = ['foo', 'bar', 'response', 'end']; + var options; + before(function () { + var foo_interceptor = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + metadata.add(keys.foo, values.foo); + registry.addCall('foo'); + next(metadata, listener); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + var bar_interceptor = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + metadata.add(keys.bar, values.bar); + registry.addCall('bar'); + next(metadata, listener); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + options = { interceptors: [foo_interceptor, bar_interceptor] }; + }); + + it('with unary call', function (done) { + registry = new CallRegistry(done, expected_calls, true); + var metadata = new Metadata(); + metadata.add(keys.original, values.original); + + var unary_call = client.echo(message, metadata, options, function (error, response) { + assert.ifError(error); + registry.addCall('end'); + }); + unary_call.on('metadata', function (metadata) { + var has_expected_values = _.every(key_names, function (key_name) { + return _.isEqual(metadata.get(keys[key_name]), [values[key_name]]); + }); + assert(has_expected_values); + registry.addCall('response'); + }); + }); + it('with client streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var metadata = new Metadata(); + metadata.add(keys.original, values.original); + + var client_stream = client.echoClientStream(metadata, options, + function (error, response) { + assert.ifError(error); + registry.addCall('end'); + }); + client_stream.write(message); + client_stream.on('metadata', function (metadata) { + var has_expected_values = _.every(key_names, function (key_name) { + return _.isEqual(metadata.get(keys[key_name]), [values[key_name]]); + }); + assert(has_expected_values); + registry.addCall('response'); + }); + client_stream.end(); + }); + it('with server streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var metadata = new Metadata(); + metadata.add(keys.original, values.original); + var server_stream = client.echoServerStream(message, metadata, options); + server_stream.on('metadata', function (metadata) { + var has_expected_values = _.every(key_names, function (key_name) { + return _.isEqual(metadata.get(keys[key_name]), [values[key_name]]); + }); + assert(has_expected_values); + registry.addCall('response'); + }); + server_stream.on('data', function() { }); + server_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('end'); + }); + server_stream.on('error', (error) => { + assert.ifError(error); + }); + }); + it('with bidi streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var metadata = new Metadata(); + metadata.add(keys.original, values.original); + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('metadata', function(metadata) { + var has_expected_values = _.every(key_names, function(key_name) { + return _.isEqual(metadata.get(keys[key_name]),[values[key_name]]); + }); + assert(has_expected_values); + bidi_stream.end(); + registry.addCall('response'); + }); + bidi_stream.on('data', function() { }); + bidi_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('end'); + }); + bidi_stream.on('error', (error) => { + assert.ifError(error); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + + describe('trigger when sending messages', function() { + var registry; + var originalValue = 'foo'; + var expectedValue = 'bar'; + var options; + var metadata = new Metadata(); + var expected_calls = ['messageIntercepted', 'response', 'end']; + + before(function() { + var foo_interceptor = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withSendMessage(function (message, next) { + assert.strictEqual(message.value, originalValue); + registry.addCall('messageIntercepted'); + next({ value: expectedValue }); + }).build(); + return new InterceptingCall(nextCall(options), + outbound); + }; + options = { interceptors: [foo_interceptor] }; + }); + + it('with unary call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var message = {value: originalValue}; + + client.echo(message, metadata, options, function (err, response) { + assert.ifError(err); + assert.strictEqual(response.value, expectedValue); + registry.addCall('response'); + registry.addCall('end'); + }); + }); + it('with client streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var message = {value: originalValue}; + var client_stream = client.echoClientStream(metadata, options, + function (err, response) { + assert.ifError(err); + assert.strictEqual(response.value, expectedValue); + registry.addCall('response'); + registry.addCall('end'); + }); + client_stream.write(message); + client_stream.end(); + }); + it('with server streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var message = {value: originalValue}; + var server_stream = client.echoServerStream(message, metadata, options); + server_stream.on('data', function (data) { + assert.strictEqual(data.value, expectedValue); + registry.addCall('response'); + }); + server_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('end'); + }); + server_stream.on('error', (error) => { + assert.ifError(error); + }); + }); + it('with bidi streaming call', function(done) { + registry = new CallRegistry(done, expected_calls, true); + var message = {value: originalValue}; + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('data', function(data) { + assert.strictEqual(data.value, expectedValue); + registry.addCall('response'); + }); + bidi_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('end'); + }); + bidi_stream.on('error', (error) => { + assert.ifError(error); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + + describe('trigger when client closes the call', function() { + var registry; + var expected_calls = [ + 'response', 'halfClose', 'end' + ]; + var message = {}; + var options; + before(function() { + var foo_interceptor = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withHalfClose(function (next) { + registry.addCall('halfClose'); + next(); + }).build(); + return new InterceptingCall(nextCall(options), + outbound); + }; + options = { interceptors: [foo_interceptor] }; + }); + it('with unary call', function (done) { + registry = new CallRegistry(done, expected_calls); + client.echo(message, options, function (err, response) { + assert.ifError(err); + registry.addCall('response'); + registry.addCall('end'); + }); + }); + it('with client streaming call', function (done) { + registry = new CallRegistry(done, expected_calls); + var client_stream = client.echoClientStream(options, + function (err, response) { + assert.ifError(err); + registry.addCall('response'); + registry.addCall('end'); + }); + client_stream.write(message); + client_stream.end(); + }); + it('with server streaming call', function (done) { + registry = new CallRegistry(done, expected_calls); + var server_stream = client.echoServerStream(message, options); + server_stream.on('data', function (data) { + registry.addCall('response'); + }); + server_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('end'); + }); + server_stream.on('error', (error) => { + assert.ifError(error); + }); + }); + it('with bidi streaming call', function (done) { + registry = new CallRegistry(done, expected_calls); + var bidi_stream = client.echoBidiStream(options); + bidi_stream.on('data', function (data) { + registry.addCall('response'); + }); + bidi_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + registry.addCall('end'); + }); + bidi_stream.on('error', (error) => { + assert.ifError(error); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + + describe('trigger when the stream is canceled', function() { + var done; + var message = {}; + var options; + before(function() { + var foo_interceptor = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withCancel(function (next) { + done(); + next(); + }).build(); + return new InterceptingCall(nextCall(options), + outbound); + }; + options = { interceptors: [foo_interceptor] }; + }); + + it('with unary call', function(cb) { + done = cb; + var stream = client.echo(message, options, function() {}); + stream.cancel(); + }); + + it('with client streaming call', function(cb) { + done = cb; + var stream = client.echoClientStream(options, function() {}); + stream.cancel(); + }); + it('with server streaming call', function(cb) { + done = cb; + var stream = client.echoServerStream(message, options); + stream.on('error', (error) => { + assert.strictEqual(error.code, clientGrpc.status.CANCELLED); + }); + stream.cancel(); + }); + it('with bidi streaming call', function(cb) { + done = cb; + var stream = client.echoBidiStream(options); + stream.on('error', (error) => { + assert.strictEqual(error.code, clientGrpc.status.CANCELLED); + }); + stream.cancel(); + }); + }); + + describe('trigger when receiving metadata', function() { + var message = {}; + var expectedKey = 'foo'; + var expectedValue = 'bar'; + var options; + before(function() { + var foo_interceptor = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + var new_listener = (new ListenerBuilder()).withOnReceiveMetadata( + function (metadata, next) { + metadata.add(expectedKey, expectedValue); + next(metadata); + }).build(); + next(metadata, new_listener); + }).build(); + return new InterceptingCall(nextCall(options), outbound); + }; + options = { interceptors: [foo_interceptor] }; + }); + + it('with unary call', function(done) { + var metadata = new Metadata(); + var unary_call = client.echo(message, metadata, options, function () {}); + unary_call.on('metadata', function (metadata) { + assert.strictEqual(metadata.get(expectedKey)[0], expectedValue); + done(); + }); + }); + it('with client streaming call', function(done) { + var metadata = new Metadata(); + var client_stream = client.echoClientStream(metadata, options, + function () {}); + client_stream.write(message); + client_stream.on('metadata', function (metadata) { + assert.strictEqual(metadata.get(expectedKey)[0], expectedValue); + done(); + }); + client_stream.end(); + }); + it('with server streaming call', function(done) { + var metadata = new Metadata(); + var server_stream = client.echoServerStream(message, metadata, options); + server_stream.on('metadata', function (metadata) { + assert.strictEqual(metadata.get(expectedKey)[0], expectedValue); + done(); + }); + server_stream.on('error', (error) => { + assert.ifError(error); + }); + server_stream.on('data', function() { }); + }); + it('with bidi streaming call', function(done) { + var metadata = new Metadata(); + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('metadata', function(metadata) { + assert.strictEqual(metadata.get(expectedKey)[0], expectedValue); + bidi_stream.end(); + done(); + }); + bidi_stream.on('data', function() { }); + bidi_stream.on('error', (error) => { + assert.ifError(error); + }) + bidi_stream.write(message); + }); + }); + + describe('trigger when sending messages', function() { + var originalValue = 'foo'; + var expectedValue = 'bar'; + var options; + var metadata = new Metadata(); + before(function() { + var foo_interceptor = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + var new_listener = (new ListenerBuilder()).withOnReceiveMessage( + function (message, next) { + if (!message) { + next(message); + return; + } + assert.strictEqual(message.value, originalValue); + message.value = expectedValue; + next(message); + }).build(); + next(metadata, new_listener); + }).build(); + return new InterceptingCall(nextCall(options), + outbound); + }; + options = { interceptors: [foo_interceptor] }; + }); + + it('with unary call', function (done) { + var message = { value: originalValue }; + client.echo(message, metadata, options, function (err, response) { + assert.ifError(err); + assert.strictEqual(response.value, expectedValue); + done(); + }); + }); + it('with client streaming call', function (done) { + var message = { value: originalValue }; + var client_stream = client.echoClientStream(metadata, options, + function (err, response) { + assert.ifError(err); + assert.strictEqual(response.value, expectedValue); + done(); + }); + client_stream.write(message); + client_stream.end(); + }); + it('with server streaming call', function (done) { + var message = { value: originalValue }; + var server_stream = client.echoServerStream(message, metadata, options); + server_stream.on('data', function (data) { + assert.strictEqual(data.value, expectedValue); + done(); + }); + server_stream.on('error', (error) => { + assert.ifError(error); + }); + }); + it('with bidi streaming call', function (done) { + var message = { value: originalValue }; + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('data', function (data) { + assert.strictEqual(data.value, expectedValue); + done(); + }); + bidi_stream.on('error', (error) => { + assert.ifError(error); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + + describe('trigger when receiving status', function() { + var expectedStatus = 'foo'; + var options; + var metadata = new Metadata(); + before(function() { + var foo_interceptor = function (options, nextCall) { + var outbound = (new RequesterBuilder()) + .withStart(function (metadata, listener, next) { + var new_listener = (new ListenerBuilder()).withOnReceiveStatus( + function (status, next) { + assert.strictEqual(status.code, 2); + assert.strictEqual(status.details, 'test status message'); + var new_status = { + code: 1, + details: expectedStatus, + metadata: {} + }; + next(new_status); + }).build(); + next(metadata, new_listener); + }).build(); + return new InterceptingCall(nextCall(options), + outbound); + }; + options = { interceptors: [foo_interceptor] }; + }); + it('with unary call', function (done) { + var message = { value: 'error' }; + var unary_call = client.echo(message, metadata, options, function () { + }); + unary_call.on('status', function (status) { + assert.strictEqual(status.code, 1); + assert.strictEqual(status.details, expectedStatus); + done(); + }); + }); + it('with client streaming call', function (done) { + var message = { value: 'error' }; + var client_stream = client.echoClientStream(metadata, options, + function () { + }); + client_stream.on('status', function (status) { + assert.strictEqual(status.code, 1); + assert.strictEqual(status.details, expectedStatus); + done(); + }); + client_stream.write(message); + client_stream.end(); + }); + + it('with server streaming call', function(done) { + var message = {value: 'error'}; + var server_stream = client.echoServerStream(message, metadata, options); + server_stream.on('error', function (err) { + }); + server_stream.on('data', function (data) { + }); + server_stream.on('status', function (status) { + assert.strictEqual(status.code, 1); + assert.strictEqual(status.details, expectedStatus); + done(); + }); + }); + + it('with bidi streaming call', function(done) { + var message = {value: 'error'}; + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('error', function(err) {}); + bidi_stream.on('data', function(data) {}); + bidi_stream.on('status', function(status) { + assert.strictEqual(status.code, 1); + assert.strictEqual(status.details, expectedStatus); + done(); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + describe('delay streaming headers', function() { + var options; + var metadata = new Metadata(); + before(function() { + var foo_interceptor = function (options, nextCall) { + var startNext; + var startListener; + var startMetadata; + var methods = { + start: function (metadata, listener, next) { + startNext = next; + startListener = listener; + startMetadata = metadata; + }, + sendMessage: function (message, next) { + startMetadata.set('fromMessage', message.value); + startNext(startMetadata, startListener); + next(message); + } + }; + return new InterceptingCall(nextCall(options), methods); + }; + options = { interceptors: [foo_interceptor] }; + }); + + it('with client streaming call', function (done) { + var message = { value: 'foo' }; + var client_stream = client.echoClientStream(metadata, options, + function (error, response) { + assert.ifError(error); + done(); + }); + client_stream.on('metadata', function (metadata) { + assert.equal(metadata.get('fromMessage'), 'foo'); + }); + client_stream.write(message); + client_stream.end(); + }); + it('with bidi streaming call', function (done) { + var message = { value: 'foo' }; + var bidi_stream = client.echoBidiStream(metadata, options); + bidi_stream.on('metadata', function (metadata) { + assert.equal(metadata.get('fromMessage'), 'foo'); + }); + bidi_stream.on('data', () => {}); + bidi_stream.on('status', (status) => { + assert.strictEqual(status.code, clientGrpc.status.OK); + done(); + }); + bidi_stream.on('error', (error) => { + assert.ifError(error); + }); + bidi_stream.write(message); + bidi_stream.end(); + }); + }); + }); +}); diff --git a/test/api/connectivity_test.js b/test/api/connectivity_test.js new file mode 100644 index 000000000..64764d7d4 --- /dev/null +++ b/test/api/connectivity_test.js @@ -0,0 +1,148 @@ +/* + * Copyright 2019 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. + * + */ + +const options = { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true +}; +const path = require('path'); +const fs = require('fs'); +const assert = require('assert'); +const _ = require('lodash'); +const anyGrpc = require('../any_grpc'); +const clientGrpc = anyGrpc.client; +const serverGrpc = anyGrpc.server; +const protoLoader = require('../../packages/proto-loader', options); +const testServiceDef = protoLoader.loadSync(__dirname + '/../proto/test_service.proto'); +const TestService = serverGrpc.loadPackageDefinition(testServiceDef).TestService.service; +const TestServiceClient = clientGrpc.loadPackageDefinition(testServiceDef).TestService; + +const clientCreds = clientGrpc.credentials.createInsecure(); +const serverCreds = serverGrpc.ServerCredentials.createInsecure(); + +const serviceImpl = { + unary: function(call, cb) { + cb(null, {}); + }, + clientStream: function(stream, cb){ + stream.on('data', function(data) {}); + stream.on('end', function() { + cb(null, {}); + }); + }, + serverStream: function(stream) { + stream.end(); + }, + bidiStream: function(stream) { + stream.on('data', function(data) {}); + stream.on('end', function() { + stream.end(); + }); + } +}; + +describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, function() { + it('client should not wait for ready by default', function(done) { + this.timeout(15000); + /* TCP port 47 is reserved according to + * https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers */ + const disconnectedClient = new TestServiceClient('localhost:47', clientGrpc.credentials.createInsecure()); + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 10); + disconnectedClient.unary({}, {deadline: deadline}, (error, value) =>{ + assert(error); + assert.strictEqual(error.code, clientGrpc.status.UNAVAILABLE); + done(); + }); + }); + it('client should wait for a connection with waitForReady on', function(done) { + this.timeout(15000); + const disconnectedClient = new TestServiceClient('localhost:47', clientGrpc.credentials.createInsecure()); + const metadata = new clientGrpc.Metadata({waitForReady: true}); + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 10); + disconnectedClient.unary({}, metadata, {deadline: deadline}, (error, value) =>{ + assert(error); + assert.strictEqual(error.code, clientGrpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); + describe('Reconnection', function() { + let server1; + let server2; + let port; + before(function(done) { + server1 = new serverGrpc.Server(); + server1.addService(TestService, serviceImpl); + server2 = new serverGrpc.Server(); + server2.addService(TestService, serviceImpl); + server1.bindAsync('localhost:0', serverCreds, (err, _port) => { + assert.ifError(err); + server1.start(); + port = _port; + client = new TestServiceClient(`localhost:${port}`, clientCreds); + done(); + }); + }); + after(function() { + client.close(); + server1.forceShutdown(); + server2.forceShutdown(); + }); + it.skip('Should end with either OK or UNAVAILABLE when querying a server that is shutting down', function(done) { + this.timeout(10000); + let pendingCalls = 0; + let testDone = false; + let callInterval; + function maybeDone() { + if (testDone && pendingCalls === 0) { + done(); + } + }; + client.unary({}, (err, data) => { + assert.ifError(err); + server1.tryShutdown(() => { + server2.bindAsync(`localhost:${port}`, serverCreds, (err) => { + assert.ifError(err); + server2.start(); + const metadata = new clientGrpc.Metadata({ waitForReady: true }); + client.unary({}, metadata, (err, data) => { + assert.ifError(err); + clearInterval(callInterval); + testDone = true; + maybeDone(); + }); + }); + }); + callInterval = setInterval(() => { + assert.strictEqual(testDone, false); + pendingCalls += 1; + client.unary({}, (err, data) => { + pendingCalls -= 1; + if (err) { + assert.strictEqual(err.code, clientGrpc.status.UNAVAILABLE); + } + maybeDone(); + }); + }, 0); + }); + }); + }); +}); diff --git a/test/api/credentials_test.js b/test/api/credentials_test.js deleted file mode 100644 index 968799945..000000000 --- a/test/api/credentials_test.js +++ /dev/null @@ -1,452 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); -var fs = require('fs'); -var path = require('path'); - -var grpc = require('grpc'); - -/** - * This is used for testing functions with multiple asynchronous calls that - * can happen in different orders. This should be passed the number of async - * function invocations that can occur last, and each of those should call this - * function's return value - * @param {function()} done The function that should be called when a test is - * complete. - * @param {number} count The number of calls to the resulting function if the - * test passes. - * @return {function()} The function that should be called at the end of each - * sequence of asynchronous functions. - */ -function multiDone(done, count) { - return function() { - count -= 1; - if (count <= 0) { - done(); - } - }; -} - -var fakeSuccessfulGoogleCredentials = { - getRequestMetadata: function(service_url, callback) { - setTimeout(function() { - callback(null, {Authorization: 'success'}); - }, 0); - } -}; - -var fakeFailingGoogleCredentials = { - getRequestMetadata: function(service_url, callback) { - setTimeout(function() { - // Google credentials currently adds string error codes to auth errors - var error = new Error('Authentication failure'); - error.code = 'ENOENT'; - callback(error); - }, 0); - } -}; - -var key_data, pem_data, ca_data; - -before(function() { - var key_path = path.join(__dirname, '../data/server1.key'); - var pem_path = path.join(__dirname, '../data/server1.pem'); - var ca_path = path.join(__dirname, '../data/ca.pem'); - key_data = fs.readFileSync(key_path); - pem_data = fs.readFileSync(pem_path); - ca_data = fs.readFileSync(ca_path); -}); - -describe('channel credentials', function() { - describe('#createSsl', function() { - it('works with no arguments', function() { - var creds; - assert.doesNotThrow(function() { - creds = grpc.credentials.createSsl(); - }); - assert.notEqual(creds, null); - }); - it('works with just one Buffer argument', function() { - var creds; - assert.doesNotThrow(function() { - creds = grpc.credentials.createSsl(ca_data); - }); - assert.notEqual(creds, null); - }); - it('works with 3 Buffer arguments', function() { - var creds; - assert.doesNotThrow(function() { - creds = grpc.credentials.createSsl(ca_data, key_data, pem_data); - }); - assert.notEqual(creds, null); - }); - it('works if the first argument is null', function() { - var creds; - assert.doesNotThrow(function() { - creds = grpc.credentials.createSsl(null, key_data, pem_data); - }); - assert.notEqual(creds, null); - }); - it('fails if the first argument is a non-Buffer value', function() { - assert.throws(function() { - grpc.credentials.createSsl('test'); - }, TypeError); - }); - it('fails if the second argument is a non-Buffer value', function() { - assert.throws(function() { - grpc.credentials.createSsl(null, 'test', pem_data); - }, TypeError); - }); - it('fails if the third argument is a non-Buffer value', function() { - assert.throws(function() { - grpc.credentials.createSsl(null, key_data, 'test'); - }, TypeError); - }); - it('fails if only 1 of the last 2 arguments is provided', function() { - assert.throws(function() { - grpc.credentials.createSsl(null, key_data); - }); - assert.throws(function() { - grpc.credentials.createSsl(null, null, pem_data); - }); - }); - }); -}); - -describe('server credentials', function() { - describe('#createSsl', function() { - it('accepts a buffer and array as the first 2 arguments', function() { - var creds; - assert.doesNotThrow(function() { - creds = grpc.ServerCredentials.createSsl(ca_data, []); - }); - assert.notEqual(creds, null); - }); - it('accepts a boolean as the third argument', function() { - var creds; - assert.doesNotThrow(function() { - creds = grpc.ServerCredentials.createSsl(ca_data, [], true); - }); - assert.notEqual(creds, null); - }); - it('accepts an object with two buffers in the second argument', function() { - var creds; - assert.doesNotThrow(function() { - creds = grpc.ServerCredentials.createSsl(null, - [{private_key: key_data, - cert_chain: pem_data}]); - }); - assert.notEqual(creds, null); - }); - it('accepts multiple objects in the second argument', function() { - var creds; - assert.doesNotThrow(function() { - creds = grpc.ServerCredentials.createSsl(null, - [{private_key: key_data, - cert_chain: pem_data}, - {private_key: key_data, - cert_chain: pem_data}]); - }); - assert.notEqual(creds, null); - }); - it('fails if the second argument is not an Array', function() { - assert.throws(function() { - grpc.ServerCredentials.createSsl(ca_data, 'test'); - }, TypeError); - }); - it('fails if the first argument is a non-Buffer value', function() { - assert.throws(function() { - grpc.ServerCredentials.createSsl('test', []); - }, TypeError); - }); - it('fails if the third argument is a non-boolean value', function() { - assert.throws(function() { - grpc.ServerCredentials.createSsl(ca_data, [], 'test'); - }, TypeError); - }); - it('fails if the array elements are not objects', function() { - assert.throws(function() { - grpc.ServerCredentials.createSsl(ca_data, 'test'); - }, TypeError); - }); - it('fails if the object does not have a Buffer private_key', function() { - assert.throws(function() { - grpc.ServerCredentials.createSsl(null, - [{private_key: 'test', - cert_chain: pem_data}]); - }, TypeError); - }); - it('fails if the object does not have a Buffer cert_chain', function() { - assert.throws(function() { - grpc.ServerCredentials.createSsl(null, - [{private_key: key_data, - cert_chain: 'test'}]); - }, TypeError); - }); - }); -}); - -describe('client credentials', function() { - var Client; - var server; - var port; - var client_ssl_creds; - var client_options = {}; - before(function() { - var proto = grpc.load(__dirname + '/test_service.proto'); - server = new grpc.Server(); - server.addService(proto.TestService.service, { - unary: function(call, cb) { - call.sendMetadata(call.metadata); - cb(null, {}); - }, - clientStream: function(stream, cb){ - stream.on('data', function(data) {}); - stream.on('end', function() { - stream.sendMetadata(stream.metadata); - cb(null, {}); - }); - }, - serverStream: function(stream) { - stream.sendMetadata(stream.metadata); - stream.end(); - }, - bidiStream: function(stream) { - stream.on('data', function(data) {}); - stream.on('end', function() { - stream.sendMetadata(stream.metadata); - stream.end(); - }); - } - }); - var creds = grpc.ServerCredentials.createSsl(null, - [{private_key: key_data, - cert_chain: pem_data}]); - port = server.bind('localhost:0', creds); - server.start(); - - Client = proto.TestService; - client_ssl_creds = grpc.credentials.createSsl(ca_data); - var host_override = 'foo.test.google.fr'; - client_options['grpc.ssl_target_name_override'] = host_override; - client_options['grpc.default_authority'] = host_override; - }); - after(function() { - server.forceShutdown(); - }); - it('Should accept SSL creds for a client', function(done) { - var client = new Client('localhost:' + port, client_ssl_creds, - client_options); - client.unary({}, function(err, data) { - assert.ifError(err); - done(); - }); - }); - it('Should update metadata with SSL creds', function(done) { - var metadataUpdater = function(service_url, callback) { - var metadata = new grpc.Metadata(); - metadata.set('plugin_key', 'plugin_value'); - callback(null, metadata); - }; - var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater); - var combined_creds = grpc.credentials.combineChannelCredentials( - client_ssl_creds, creds); - var client = new Client('localhost:' + port, combined_creds, - client_options); - var call = client.unary({}, function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); - done(); - }); - }); - it('Should update metadata for two simultaneous calls', function(done) { - done = multiDone(done, 2); - var metadataUpdater = function(service_url, callback) { - var metadata = new grpc.Metadata(); - metadata.set('plugin_key', 'plugin_value'); - callback(null, metadata); - }; - var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater); - var combined_creds = grpc.credentials.combineChannelCredentials( - client_ssl_creds, creds); - var client = new Client('localhost:' + port, combined_creds, - client_options); - var call = client.unary({}, function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); - done(); - }); - var call2 = client.unary({}, function(err, data) { - assert.ifError(err); - }); - call2.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); - done(); - }); - }); - it('should propagate errors that the updater emits', function(done) { - var metadataUpdater = function(service_url, callback) { - var error = new Error('Authentication error'); - error.code = grpc.status.UNAUTHENTICATED; - callback(error); - }; - var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater); - var combined_creds = grpc.credentials.combineChannelCredentials( - client_ssl_creds, creds); - var client = new Client('localhost:' + port, combined_creds, - client_options); - client.unary({}, function(err, data) { - assert(err); - assert.strictEqual(err.message, - 'Getting metadata from plugin failed with error: ' + - 'Authentication error'); - assert.strictEqual(err.code, grpc.status.UNAUTHENTICATED); - done(); - }); - }); - it('should successfully wrap a Google credential', function(done) { - var creds = grpc.credentials.createFromGoogleCredential( - fakeSuccessfulGoogleCredentials); - var combined_creds = grpc.credentials.combineChannelCredentials( - client_ssl_creds, creds); - var client = new Client('localhost:' + port, combined_creds, - client_options); - var call = client.unary({}, function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('authorization'), ['success']); - done(); - }); - }); - it('Should not add metadata with just SSL credentials', function(done) { - // Tests idempotency of credentials composition - var metadataUpdater = function(service_url, callback) { - var metadata = new grpc.Metadata(); - metadata.set('plugin_key', 'plugin_value'); - callback(null, metadata); - }; - var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater); - grpc.credentials.combineChannelCredentials(client_ssl_creds, creds); - var client = new Client('localhost:' + port, client_ssl_creds, - client_options); - var call = client.unary({}, function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), []); - done(); - }); - }); - it('should get an error from a Google credential', function(done) { - var creds = grpc.credentials.createFromGoogleCredential( - fakeFailingGoogleCredentials); - var combined_creds = grpc.credentials.combineChannelCredentials( - client_ssl_creds, creds); - var client = new Client('localhost:' + port, combined_creds, - client_options); - client.unary({}, function(err, data) { - assert(err); - assert.strictEqual(err.message, - 'Getting metadata from plugin failed with error: ' + - 'Authentication failure'); - done(); - }); - }); - describe('Per-rpc creds', function() { - var client; - var updater_creds; - before(function() { - client = new Client('localhost:' + port, client_ssl_creds, - client_options); - var metadataUpdater = function(service_url, callback) { - var metadata = new grpc.Metadata(); - metadata.set('plugin_key', 'plugin_value'); - callback(null, metadata); - }; - updater_creds = grpc.credentials.createFromMetadataGenerator( - metadataUpdater); - }); - it('Should update metadata on a unary call', function(done) { - var call = client.unary({}, {credentials: updater_creds}, - function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); - done(); - }); - }); - it('should update metadata on a client streaming call', function(done) { - var call = client.clientStream({credentials: updater_creds}, - function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); - done(); - }); - call.end(); - }); - it('should update metadata on a server streaming call', function(done) { - var call = client.serverStream({}, {credentials: updater_creds}); - call.on('data', function() {}); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); - done(); - }); - }); - it('should update metadata on a bidi streaming call', function(done) { - var call = client.bidiStream({credentials: updater_creds}); - call.on('data', function() {}); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); - done(); - }); - call.end(); - }); - it('should be able to use multiple plugin credentials', function(done) { - var altMetadataUpdater = function(service_url, callback) { - var metadata = new grpc.Metadata(); - metadata.set('other_plugin_key', 'other_plugin_value'); - callback(null, metadata); - }; - var alt_updater_creds = grpc.credentials.createFromMetadataGenerator( - altMetadataUpdater); - var combined_updater = grpc.credentials.combineCallCredentials( - updater_creds, alt_updater_creds); - var call = client.unary({}, {credentials: combined_updater}, - function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); - assert.deepEqual(metadata.get('other_plugin_key'), - ['other_plugin_value']); - done(); - }); - }); - }); -}); diff --git a/test/api/error_test.js b/test/api/error_test.js new file mode 100644 index 000000000..a99619fbd --- /dev/null +++ b/test/api/error_test.js @@ -0,0 +1,692 @@ +/* + * Copyright 2019 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. + * + */ + +const options = { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true +}; +const assert = require('assert'); +const _ = require('lodash'); +const anyGrpc = require('../any_grpc'); +const clientGrpc = anyGrpc.client; +const serverGrpc = anyGrpc.server; +const protoLoader = require('../../packages/proto-loader', options); +const testServiceDef = protoLoader.loadSync(__dirname + '/../proto/test_service.proto'); +const TestServiceClient = clientGrpc.loadPackageDefinition(testServiceDef).TestService; +const clientInsecureCreds = clientGrpc.credentials.createInsecure(); +const serverInsecureCreds = serverGrpc.ServerCredentials.createInsecure(); + +describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, function() { + describe('Client malformed response handling', function() { + var server; + var client; + var badArg = Buffer.from([0xFF]); + before(function(done) { + var malformed_test_service = { + unary: { + path: '/TestService/Unary', + requestStream: false, + responseStream: false, + requestDeserialize: _.identity, + responseSerialize: _.identity + }, + clientStream: { + path: '/TestService/ClientStream', + requestStream: true, + responseStream: false, + requestDeserialize: _.identity, + responseSerialize: _.identity + }, + serverStream: { + path: '/TestService/ServerStream', + requestStream: false, + responseStream: true, + requestDeserialize: _.identity, + responseSerialize: _.identity + }, + bidiStream: { + path: '/TestService/BidiStream', + requestStream: true, + responseStream: true, + requestDeserialize: _.identity, + responseSerialize: _.identity + } + }; + server = new serverGrpc.Server(); + server.addService(malformed_test_service, { + unary: function(call, cb) { + cb(null, badArg); + }, + clientStream: function(stream, cb) { + stream.on('data', function() {/* Ignore requests */}); + stream.on('end', function() { + cb(null, badArg); + }); + }, + serverStream: function(stream) { + stream.write(badArg); + stream.end(); + }, + bidiStream: function(stream) { + stream.on('data', function() { + // Ignore requests + stream.write(badArg); + }); + stream.on('end', function() { + stream.end(); + }); + } + }); + server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { + assert.ifError(err); + client = new TestServiceClient('localhost:' + port, clientInsecureCreds); + server.start(); + done(); + }); + }); + after(function() { + server.forceShutdown(); + }); + it('should get an INTERNAL status with a unary call', function(done) { + client.unary({}, function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + }); + it('should get an INTERNAL status with a client stream call', function(done) { + var call = client.clientStream(function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + call.write({}); + call.end(); + }); + it('should get an INTERNAL status with a server stream call', function(done) { + var call = client.serverStream({}); + call.on('data', function(){}); + call.on('error', function(err) { + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + }); + it('should get an INTERNAL status with a bidi stream call', function(done) { + var call = client.bidiStream(); + call.on('data', function(){}); + call.on('error', function(err) { + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + call.write({}); + call.end(); + }); + }); + describe('Server serialization failure handling', function() { + function serializeFail(obj) { + throw new Error('Serialization failed'); + } + var client; + var server; + before(function(done) { + var malformed_test_service = { + unary: { + path: '/TestService/Unary', + requestStream: false, + responseStream: false, + requestDeserialize: _.identity, + responseSerialize: serializeFail + }, + clientStream: { + path: '/TestService/ClientStream', + requestStream: true, + responseStream: false, + requestDeserialize: _.identity, + responseSerialize: serializeFail + }, + serverStream: { + path: '/TestService/ServerStream', + requestStream: false, + responseStream: true, + requestDeserialize: _.identity, + responseSerialize: serializeFail + }, + bidiStream: { + path: '/TestService/BidiStream', + requestStream: true, + responseStream: true, + requestDeserialize: _.identity, + responseSerialize: serializeFail + } + }; + server = new serverGrpc.Server(); + server.addService(malformed_test_service, { + unary: function(call, cb) { + cb(null, {}); + }, + clientStream: function(stream, cb) { + stream.on('data', function() {/* Ignore requests */}); + stream.on('end', function() { + cb(null, {}); + }); + }, + serverStream: function(stream) { + stream.write({}); + stream.end(); + }, + bidiStream: function(stream) { + stream.on('data', function() { + // Ignore requests + stream.write({}); + }); + stream.on('end', function() { + stream.end(); + }); + } + }); + server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { + assert.ifError(err); + client = new TestServiceClient('localhost:' + port, clientInsecureCreds); + server.start(); + done(); + }); + }); + after(function() { + server.forceShutdown(); + }); + it('should get an INTERNAL status with a unary call', function(done) { + client.unary({}, function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + }); + it('should get an INTERNAL status with a client stream call', function(done) { + var call = client.clientStream(function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + call.write({}); + call.end(); + }); + it('should get an INTERNAL status with a server stream call', function(done) { + var call = client.serverStream({}); + call.on('data', function(){}); + call.on('error', function(err) { + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + }); + it('should get an INTERNAL status with a bidi stream call', function(done) { + var call = client.bidiStream(); + call.on('data', function(){}); + call.on('error', function(err) { + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + call.write({}); + call.end(); + }); + }); + describe('Other conditions', function() { + var Client; + var client; + var server; + var port; + before(function(done) { + server = new serverGrpc.Server(); + var trailer_metadata = new serverGrpc.Metadata(); + trailer_metadata.add('trailer-present', 'yes'); + server.addService(TestServiceClient.service, { + unary: function(call, cb) { + var req = call.request; + if (req.error) { + var message = 'Requested error'; + if (req.message) { + message = req.message; + } + cb({code: serverGrpc.status.UNKNOWN, + details: message}, null, trailer_metadata); + } else { + cb(null, {count: 1}, trailer_metadata); + } + }, + clientStream: function(stream, cb){ + var count = 0; + var errored; + stream.on('data', function(data) { + if (data.error) { + var message = 'Requested error'; + if (data.message) { + message = data.message; + } + errored = true; + cb(new Error(message), null, trailer_metadata); + } else { + count += 1; + } + }); + stream.on('end', function() { + if (!errored) { + cb(null, {count: count}, trailer_metadata); + } + }); + }, + serverStream: function(stream) { + var req = stream.request; + if (req.error) { + var message = 'Requested error'; + if (req.message) { + message = req.message; + } + var err = {code: serverGrpc.status.UNKNOWN, + details: message}; + err.metadata = trailer_metadata; + stream.emit('error', err); + } else { + for (var i = 0; i < 5; i++) { + stream.write({count: i}); + } + stream.end(trailer_metadata); + } + }, + bidiStream: function(stream) { + var count = 0; + stream.on('data', function(data) { + if (data.error) { + var message = 'Requested error'; + if (data.message) { + message = data.message; + } + var err = new Error(message); + err.metadata = trailer_metadata.clone(); + err.metadata.add('count', '' + count); + stream.emit('error', err); + } else { + stream.write({count: count}); + count += 1; + } + }); + stream.on('end', function() { + stream.end(trailer_metadata); + }); + } + }); + server.bindAsync('localhost:0', serverInsecureCreds, (err, _port) => { + assert.ifError(err); + port = _port; + client = new TestServiceClient('localhost:' + port, clientInsecureCreds); + server.start(); + done(); + }); + }); + after(function() { + server.forceShutdown(); + }); + describe('Server recieving bad input', function() { + var misbehavingClient; + var badArg = Buffer.from([0xFF]); + before(function() { + var test_service_attrs = { + unary: { + path: '/TestService/Unary', + requestStream: false, + responseStream: false, + requestSerialize: _.identity, + responseDeserialize: _.identity + }, + clientStream: { + path: '/TestService/ClientStream', + requestStream: true, + responseStream: false, + requestSerialize: _.identity, + responseDeserialize: _.identity + }, + serverStream: { + path: '/TestService/ServerStream', + requestStream: false, + responseStream: true, + requestSerialize: _.identity, + responseDeserialize: _.identity + }, + bidiStream: { + path: '/TestService/BidiStream', + requestStream: true, + responseStream: true, + requestSerialize: _.identity, + responseDeserialize: _.identity + } + }; + var Client = clientGrpc.makeGenericClientConstructor(test_service_attrs, + 'TestService'); + misbehavingClient = new Client('localhost:' + port, clientInsecureCreds); + }); + it('should respond correctly to a unary call', function(done) { + misbehavingClient.unary(badArg, function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + }); + it('should respond correctly to a client stream', function(done) { + var call = misbehavingClient.clientStream(function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + call.write(badArg); + // TODO(mlumish): Remove call.end() + call.end(); + }); + it('should respond correctly to a server stream', function(done) { + var call = misbehavingClient.serverStream(badArg); + call.on('data', function(data) { + assert.fail(data, null, 'Unexpected data', '==='); + }); + call.on('error', function(err) { + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + }); + it('should respond correctly to a bidi stream', function(done) { + var call = misbehavingClient.bidiStream(); + call.on('data', function(data) { + assert.fail(data, null, 'Unexpected data', '==='); + }); + call.on('error', function(err) { + assert.strictEqual(err.code, clientGrpc.status.INTERNAL); + done(); + }); + call.write(badArg); + // TODO(mlumish): Remove call.end() + call.end(); + }); + }); + describe('Trailing metadata', function() { + it('should be present when a unary call succeeds', function(done) { + var call = client.unary({error: false}, function(err, data) { + assert.ifError(err); + }); + call.on('status', function(status) { + assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + it('should be present when a unary call fails', function(done) { + var call = client.unary({error: true}, function(err, data) { + assert(err); + }); + call.on('status', function(status) { + assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + it('should be present when a client stream call succeeds', function(done) { + var call = client.clientStream(function(err, data) { + assert.ifError(err); + }); + call.write({error: false}); + call.write({error: false}); + call.end(); + call.on('status', function(status) { + assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + it('should be present when a client stream call fails', function(done) { + var call = client.clientStream(function(err, data) { + assert(err); + }); + call.write({error: false}); + call.write({error: true}); + call.end(); + call.on('status', function(status) { + assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + it('should be present when a server stream call succeeds', function(done) { + var call = client.serverStream({error: false}); + call.on('data', function(){}); + call.on('status', function(status) { + assert.strictEqual(status.code, clientGrpc.status.OK); + assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + it('should be present when a server stream call fails', function(done) { + var call = client.serverStream({error: true}); + call.on('data', function(){}); + call.on('error', function(error) { + assert.deepEqual(error.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + it('should be present when a bidi stream succeeds', function(done) { + var call = client.bidiStream(); + call.write({error: false}); + call.write({error: false}); + call.end(); + call.on('data', function(){}); + call.on('status', function(status) { + assert.strictEqual(status.code, clientGrpc.status.OK); + assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + it('should be present when a bidi stream fails', function(done) { + var call = client.bidiStream(); + call.write({error: false}); + call.write({error: true}); + call.end(); + call.on('data', function(){}); + call.on('error', function(error) { + assert.deepEqual(error.metadata.get('trailer-present'), ['yes']); + done(); + }); + }); + }); + describe('Error object should contain the status', function() { + it('for a unary call', function(done) { + client.unary({error: true}, function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.UNKNOWN); + assert.strictEqual(err.details, 'Requested error'); + done(); + }); + }); + it('for a client stream call', function(done) { + var call = client.clientStream(function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.UNKNOWN); + assert.strictEqual(err.details, 'Requested error'); + done(); + }); + call.write({error: false}); + call.write({error: true}); + call.end(); + }); + it('for a server stream call', function(done) { + var call = client.serverStream({error: true}); + call.on('data', function(){}); + call.on('error', function(error) { + assert.strictEqual(error.code, clientGrpc.status.UNKNOWN); + assert.strictEqual(error.details, 'Requested error'); + done(); + }); + }); + it('for a bidi stream call', function(done) { + var call = client.bidiStream(); + call.write({error: false}); + call.write({error: true}); + call.end(); + call.on('data', function(){}); + call.on('error', function(error) { + assert.strictEqual(error.code, clientGrpc.status.UNKNOWN); + assert.strictEqual(error.details, 'Requested error'); + done(); + }); + }); + it('for a UTF-8 error message', function(done) { + client.unary({error: true, message: '測試字符串'}, function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.UNKNOWN); + assert.strictEqual(err.details, '測試字符串'); + done(); + }); + }); + it('for an error message with a comma', function(done) { + client.unary({error: true, message: 'a message, with a comma'}, function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.UNKNOWN); + assert.strictEqual(err.details, 'a message, with a comma'); + done(); + }); + }); + }); + }); + describe('Other conditions', function() { + var client; + var server; + var port; + before(function(done) { + server = new serverGrpc.Server(); + var existing_metadata = new serverGrpc.Metadata(); + existing_metadata.add('existing-metadata', 'yes'); + server.addService(TestServiceClient.service, { + unary: function(call, cb) { + var req = call.request; + if (req.error) { + var message = 'Requested error'; + if (req.message) { + message = req.message; + } + cb({code: serverGrpc.status.UNKNOWN, + message: message, + metadata: existing_metadata}, null, null); + } else { + cb(null, {count: 1}); + } + }/*, + clientStream: function(stream, cb){ + var count = 0; + var errored; + stream.on('data', function(data) { + if (data.error) { + var message = 'Requested error'; + if (data.message) { + message = data.message; + } + errored = true; + cb(new Error(message), null, trailer_metadata); + } else { + count += 1; + } + }); + stream.on('end', function() { + if (!errored) { + cb(null, {count: count}, trailer_metadata); + } + }); + }, + serverStream: function(stream) { + var req = stream.request; + if (req.error) { + var message = 'Requested error'; + if (req.message) { + message = req.message; + } + var err = {code: serverGrpc.status.UNKNOWN, + details: message}; + err.metadata = trailer_metadata; + stream.emit('error', err); + } else { + for (var i = 0; i < 5; i++) { + stream.write({count: i}); + } + stream.end(trailer_metadata); + } + }, + bidiStream: function(stream) { + var count = 0; + stream.on('data', function(data) { + if (data.error) { + var message = 'Requested error'; + if (data.message) { + message = data.message; + } + var err = new Error(message); + err.metadata = trailer_metadata.clone(); + err.metadata.add('count', '' + count); + stream.emit('error', err); + } else { + stream.write({count: count}); + count += 1; + } + }); + stream.on('end', function() { + stream.end(trailer_metadata); + }); + } + */}); + server.bindAsync('localhost:0', serverInsecureCreds, (err, _port) => { + assert.ifError(err); + port = _port; + client = new TestServiceClient('localhost:' + port, clientInsecureCreds); + server.start(); + done(); + }); + }); + after(function() { + server.forceShutdown(); + }); + describe('Existing metadata', function() { + it('should not be present when a unary call succeeds', function(done) { + var call = client.unary({error: false}, function(err, data) { + assert.ifError(err); + }); + call.on('status', function(status) { + assert.deepStrictEqual(status.metadata.get('existing-metadata'), []); + done(); + }); + }); + it('should be present when a unary call fails', function(done) { + var call = client.unary({error: true}, function(err, data) { + assert(err); + }); + call.on('status', function(status) { + assert.deepStrictEqual(status.metadata.get('existing-metadata'), ['yes']); + done(); + }); + }); + }); + describe('Error object should contain the status', function() { + it('for a unary call', function(done) { + client.unary({error: true}, function(err, data) { + assert(err); + assert.strictEqual(err.code, clientGrpc.status.UNKNOWN); + assert.strictEqual(err.details, 'Requested error'); + done(); + }); + }); + }); + }); +}); diff --git a/test/api/interop_extra_test.js b/test/api/interop_extra_test.js new file mode 100644 index 000000000..fa7e03276 --- /dev/null +++ b/test/api/interop_extra_test.js @@ -0,0 +1,359 @@ +/* + * + * Copyright 2019 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. + * + */ + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const interopServer = require('../interop/interop_server.js'); +const anyGrpc = require('../any_grpc'); +const grpc = anyGrpc.client; +var protoLoader = require('../../packages/proto-loader'); + +const protoPackage = protoLoader.loadSync( + 'src/proto/grpc/testing/test.proto', + {keepCase: true, + defaults: true, + enums: String, + includeDirs: [__dirname + '/../proto/']}); +const testProto = grpc.loadPackageDefinition(protoPackage).grpc.testing; + +function multiDone(done, count) { + return function() { + count -= 1; + if (count <= 0) { + done(); + } + }; +} + +function echoMetadataGenerator(options, callback) { + const metadata = new grpc.Metadata(); + metadata.set('x-grpc-test-echo-initial', 'test_initial_metadata_value'); + callback(null, metadata); +} + +const credentials = grpc.credentials.createFromMetadataGenerator(echoMetadataGenerator); + +describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, function() { + describe('Interop-adjacent tests', function() { + let server; + let client; + let port; + before(function(done) { + /* To make testing max message size enforcement easier, the we explicitly + * remove the limit on the size of messages the server can receive, and + * we expect that the size of messages it can send is unlimited by + * default. On the other side, we explicitly limit the size of messages + * the client can send to 4 MB, and we expect that the size of messages + * it can receive is limited to 4 MB by default */ + interopServer.getServer(0, true, (err, serverObj) => { + if (err) { + done(err); + } else { + server = serverObj.server; + port = serverObj.port; + server.start(); + const ca_path = path.join(__dirname, '../data/ca.pem'); + const ca_data = fs.readFileSync(ca_path); + const creds = grpc.credentials.createSsl(ca_data); + const options = { + 'grpc.ssl_target_name_override': 'foo.test.google.fr', + 'grpc.default_authority': 'foo.test.google.fr', + 'grpc.max_send_message_length': 4*1024*1024, + 'grpc.max_metadata_size': 4*1024*1024 + }; + client = new testProto.TestService(`localhost:${port}`, creds, options); + done(); + } + }, { + 'grpc.max_receive_message_length': -1, + 'grpc.max_metadata_size': 4*1024*1024 + }); + }); + after(function() { + server.forceShutdown(); + }); + it('Should be able to start many concurrent calls', function(done) { + const callCount = 100; + done = multiDone(done, callCount); + for (let i = 0; i < callCount; i++) { + client.unaryCall({}, (error, result) => { + assert.ifError(error); + done(); + }); + } + }); + it('Should echo metadata from call credentials', function(done) { + done = multiDone(done, 2); + const call = client.unaryCall({}, {credentials}, (error, result) => { + assert.ifError(error); + done(); + }); + call.on('metadata', (metadata) => { + assert.deepEqual(metadata.get('x-grpc-test-echo-initial'), + ['test_initial_metadata_value']); + done(); + }); + }); + it('Should be able to send the same metadata on two calls with call creds', function(done) { + done = multiDone(done, 5); + const metadata = new grpc.Metadata(); + metadata.set('x-grpc-test-echo-trailing-bin', Buffer.from('ababab', 'hex')); + const call1 = client.unaryCall({}, metadata, {credentials}, (error, result) => { + assert.ifError(error); + const call2 = client.unaryCall({}, metadata, {credentials}, (error, result) => { + assert.ifError(error); + done(); + }); + call2.on('metadata', (metadata) => { + assert.deepEqual(metadata.get('x-grpc-test-echo-initial'), + ['test_initial_metadata_value']); + done(); + }); + call2.on('status', function(status) { + var echo_trailer = status.metadata.get('x-grpc-test-echo-trailing-bin'); + assert(echo_trailer.length === 1); + assert.strictEqual(echo_trailer[0].toString('hex'), 'ababab'); + done(); + }); + }); + call1.on('metadata', (metadata) => { + assert.deepEqual(metadata.get('x-grpc-test-echo-initial'), + ['test_initial_metadata_value']); + done(); + }); + call1.on('status', function(status) { + var echo_trailer = status.metadata.get('x-grpc-test-echo-trailing-bin'); + assert(echo_trailer.length === 1); + assert.strictEqual(echo_trailer[0].toString('hex'), 'ababab'); + done(); + }); + }); + it('should receive all messages in a long stream', function(done) { + // the test is slow under aarch64 emulator + this.timeout(80000); + var arg = { + response_type: 'COMPRESSABLE', + response_parameters: [ + ] + }; + for (let i = 0; i < 100000; i++) { + arg.response_parameters.push({size: 0}); + } + var call = client.streamingOutputCall(arg); + let responseCount = 0; + call.on('data', (value) => { + responseCount++; + }); + call.on('status', (status) => { + assert.strictEqual(status.code, grpc.status.OK); + assert.strictEqual(responseCount, arg.response_parameters.length); + done(); + }); + call.on('error', (error) => { + assert.ifError(error); + }); + }); + /* The test against the JS server does not work because of + * https://github.com/nodejs/node/issues/35218. The test against the native + * server fails because of an unidentified timeout issue. */ + it.skip('should be able to send very large headers and trailers', function(done) { + done = multiDone(done, 3); + const header = 'X'.repeat(64 * 1024); + const trailer = Buffer.from('Y'.repeat(64 * 1024)); + const metadata = new grpc.Metadata(); + metadata.set('x-grpc-test-echo-initial', header); + metadata.set('x-grpc-test-echo-trailing-bin', trailer); + const call = client.unaryCall({}, metadata, (error, result) => { + assert.ifError(error); + done(); + }); + call.on('metadata', (metadata) => { + assert.deepStrictEqual(metadata.get('x-grpc-test-echo-initial'), + [header]); + done(); + }); + call.on('status', (status) => { + var echo_trailer = status.metadata.get('x-grpc-test-echo-trailing-bin'); + assert(echo_trailer.length === 1); + assert.strictEqual(echo_trailer[0].toString('ascii'), 'Y'.repeat(64 * 1024)); + done(); + }); + }); + describe('max message size', function() { + // with the default timeout the test times out under aarch64 emulator + this.timeout(6000); + // A size that is larger than the default limit + const largeMessageSize = 8 * 1024 * 1024; + const largeMessage = Buffer.alloc(largeMessageSize); + it('should get an error when sending a large message', function(done) { + done = multiDone(done, 2); + const unaryMessage = {payload: {body: largeMessage}}; + client.unaryCall(unaryMessage, (error, result) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + const stream = client.fullDuplexCall(); + stream.write({payload: {body: largeMessage}}); + stream.end(); + stream.on('data', () => {}); + stream.on('status', (status) => { + assert.strictEqual(status.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + stream.on('error', (error) => { + }); + }); + it('should get an error when receiving a large message', function(done) { + done = multiDone(done, 2); + client.unaryCall({response_size: largeMessageSize}, (error, result) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + const stream = client.fullDuplexCall(); + stream.write({response_parameters: [{size: largeMessageSize}]}); + stream.end(); + stream.on('data', () => {}); + stream.on('status', (status) => { + assert.strictEqual(status.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + stream.on('error', (error) => { + }); + }); + describe('with a client with no message size limits', function() { + // with the default timeout the test times out under aarch64 emulator + this.timeout(6000); + let unrestrictedClient; + before(function() { + const ca_path = path.join(__dirname, '../data/ca.pem'); + const ca_data = fs.readFileSync(ca_path); + const creds = grpc.credentials.createSsl(ca_data); + const options = { + 'grpc.ssl_target_name_override': 'foo.test.google.fr', + 'grpc.default_authority': 'foo.test.google.fr', + 'grpc.max_send_message_length': -1, + 'grpc.max_receive_message_length': -1 + }; + unrestrictedClient = new testProto.TestService(`localhost:${port}`, creds, options); + }); + it('should not get an error when sending or receiving a large message', function(done) { + done = multiDone(done, 2); + const unaryRequestMessage = { + response_size: largeMessageSize, + payload: { + body: largeMessage + } + }; + unrestrictedClient.unaryCall(unaryRequestMessage, (error, result) => { + assert.ifError(error); + assert.strictEqual(result.payload.body.length, largeMessageSize); + done(); + }); + const streamingRequestMessage = { + response_parameters: [{size: largeMessageSize}], + payload: {body: largeMessage} + }; + const stream = unrestrictedClient.fullDuplexCall(); + stream.write(streamingRequestMessage); + stream.end(); + stream.on('data', (result) => { + assert.strictEqual(result.payload.body.length, largeMessageSize); + }); + stream.on('status', () => { + done(); + }); + stream.on('error', (error) => { + assert.ifError(error); + }); + }); + }); + describe('with a server with message size limits and a client without limits', function() { + // with the default timeout the test times out under aarch64 emulator + this.timeout(6000); + let restrictedServer; + let restrictedServerClient; + let restrictedServerClient2; + let restrictedServerClient3; + let restrictedServerClient4; + before(function(done) { + interopServer.getServer(0, true, (err, serverObj) => { + if (err) { + done(err); + } else { + restrictedServer = serverObj.server; + restrictedServer.start(); + const ca_path = path.join(__dirname, '../data/ca.pem'); + const ca_data = fs.readFileSync(ca_path); + const creds = grpc.credentials.createSsl(ca_data); + const options = { + 'grpc.ssl_target_name_override': 'foo.test.google.fr', + 'grpc.default_authority': 'foo.test.google.fr', + 'grpc.max_receive_message_length': -1 + }; + restrictedServerClient = new testProto.TestService(`localhost:${serverObj.port}`, creds, options); + restrictedServerClient2 = new testProto.TestService(`localhost:${serverObj.port}`, creds, {...options, unique: 1}); + restrictedServerClient3 = new testProto.TestService(`localhost:${serverObj.port}`, creds, {...options, unique: 2}); + restrictedServerClient4 = new testProto.TestService(`localhost:${serverObj.port}`, creds, {...options, unique: 3}); + done(); + } + }, {'grpc.max_send_message_length': 4 * 1024 * 1024}); + }); + after(function() { + restrictedServer.forceShutdown(); + }); + it('should get an error when sending a large message', function(done) { + restrictedServerClient.unaryCall({payload: {body: largeMessage}}, (error, result) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + const stream = restrictedServerClient2.fullDuplexCall(); + stream.write({payload: {body: largeMessage}}); + stream.end(); + stream.on('data', () => {}); + stream.on('status', (status) => { + assert.strictEqual(status.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + stream.on('error', (error) => { + }); + }); + }); + it('should get an error when requesting a large message', function(done) { + restrictedServerClient3.unaryCall({response_size: largeMessageSize}, (error, result) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + const stream = restrictedServerClient4.fullDuplexCall(); + stream.write({response_parameters: [{size: largeMessageSize}]}); + stream.end(); + stream.on('data', () => {}); + stream.on('status', (status) => { + assert.strictEqual(status.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + stream.on('error', (error) => { + }); + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/api/interop_helper/server.js b/test/api/interop_helper/server.js new file mode 100644 index 000000000..984aa8968 --- /dev/null +++ b/test/api/interop_helper/server.js @@ -0,0 +1,32 @@ +/* + * + * Copyright 2018 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. + * + */ + +'use strict'; + +const assert = require('assert'); +const interopServer = require('../../interop/interop_server.js'); + +interopServer.getServer(0, true, (err, serverObj) => { + assert.ifError(err); + serverObj.server.start(); + process.send({port: serverObj.port}); + // The only message from the driver should be to stop the server + process.on('message', (message) => { + serverObj.server.forceShutdown(); + }); +}); diff --git a/test/api/interop_sanity_test.js b/test/api/interop_sanity_test.js index b3f65a034..c1e5d92b5 100644 --- a/test/api/interop_sanity_test.js +++ b/test/api/interop_sanity_test.js @@ -18,77 +18,91 @@ 'use strict'; -var interop_server = require('../interop/interop_server.js'); -var interop_client = require('../interop/interop_client.js'); - -var server; +var childProcess = require('child_process'); +const anyGrpc = require('../any_grpc'); var port; var name_override = 'foo.test.google.fr'; -describe('Interop tests', function() { - before(function(done) { - var server_obj = interop_server.getServer(0, true); - server = server_obj.server; - server.start(); - port = 'localhost:' + server_obj.port; - done(); - }); - after(function() { - server.forceShutdown(); - }); - // This depends on not using a binary stream - it('should pass empty_unary', function(done) { - interop_client.runTest(port, name_override, 'empty_unary', true, true, - done); - }); - // This fails due to an unknown bug - it('should pass large_unary', function(done) { - interop_client.runTest(port, name_override, 'large_unary', true, true, - done); - }); - it('should pass client_streaming', function(done) { - interop_client.runTest(port, name_override, 'client_streaming', true, true, - done); - }); - it('should pass server_streaming', function(done) { - interop_client.runTest(port, name_override, 'server_streaming', true, true, - done); - }); - it('should pass ping_pong', function(done) { - interop_client.runTest(port, name_override, 'ping_pong', true, true, done); - }); - it('should pass empty_stream', function(done) { - interop_client.runTest(port, name_override, 'empty_stream', true, true, - done); - }); - it('should pass cancel_after_begin', function(done) { - interop_client.runTest(port, name_override, 'cancel_after_begin', true, - true, done); - }); - it('should pass cancel_after_first_response', function(done) { - interop_client.runTest(port, name_override, 'cancel_after_first_response', - true, true, done); - }); - it('should pass timeout_on_sleeping_server', function(done) { - interop_client.runTest(port, name_override, 'timeout_on_sleeping_server', - true, true, done); - }); - it('should pass custom_metadata', function(done) { - interop_client.runTest(port, name_override, 'custom_metadata', - true, true, done); - }); - it('should pass status_code_and_message', function(done) { - interop_client.runTest(port, name_override, 'status_code_and_message', - true, true, done); - }); - it('should pass unimplemented_service', function(done) { - interop_client.runTest(port, name_override, 'unimplemented_service', - true, true, done); - }); - it('should pass unimplemented_method', function(done) { - interop_client.runTest(port, name_override, 'unimplemented_method', - true, true, done); +var serverProcess; + +const testCases = [ + 'empty_unary', + 'large_unary', + 'client_streaming', + 'server_streaming', + 'ping_pong', + 'empty_stream', + 'cancel_after_begin', + 'cancel_after_first_response', + 'timeout_on_sleeping_server', + 'custom_metadata', + 'status_code_and_message', + 'special_status_message', + 'unimplemented_service', + 'unimplemented_method' +]; + +var childExecArgv = []; + +describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, function() { + describe('Interop tests', function() { + // with the default timeout the test times out under aarch64 emulator + this.timeout(10000); + before(function(done) { + for (let arg of process.argv) { + if (arg.startsWith('--require=')) { + childExecArgv.push('--require'); + childExecArgv.push(arg.substring('--require='.length)); + } + } + serverProcess = childProcess.fork(`${__dirname}/interop_helper/server.js`, { + execArgv: childExecArgv + }); + serverProcess.on('message', (message) => { + port = message.port; + done(); + }); + serverProcess.on('exit', (code, signal) => { + if (code !== 0) { + if (code !== null) { + throw new Error(`Server exited with error code ${code}`); + } else { + throw new Error(`Server exited with signal ${signal}`); + } + } + }); + }); + after(function() { + serverProcess.send({}); + }); + for (let testName of testCases) { + it(`should pass ${testName}`, function(done) { + /* We need to run a client process per test to most closely match + * how the main interop test suite works */ + let clientProcess = childProcess.fork(`${__dirname}/../interop/interop_client`, [ + '--server_host=localhost', + `--server_port=${port}`, + `--server_host_override=${name_override}`, + `--test_case=${testName}`, + '--use_tls=true', + '--use_test_ca=true' + ], { + execArgv: childExecArgv + }); + clientProcess.on('exit', (code, signal) => { + if (code === 0) { + done(); + } else { + if (code !== null) { + done(new Error(`Client exited with error code ${code}`)); + } else { + done(new Error(`Client exited with signal ${signal}`)); + } + } + }); + }); + } }); -}); +}); \ No newline at end of file diff --git a/test/api/math/math_grpc_pb.js b/test/api/math/math_grpc_pb.js deleted file mode 100644 index afd08a34a..000000000 --- a/test/api/math/math_grpc_pb.js +++ /dev/null @@ -1,125 +0,0 @@ -// GENERATED CODE -- DO NOT EDIT! - -// Original file comments: -// 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. -// -'use strict'; -var grpc = require('grpc'); -var math_math_pb = require('../math/math_pb.js'); - -function serialize_DivArgs(arg) { - if (!(arg instanceof math_math_pb.DivArgs)) { - throw new Error('Expected argument of type DivArgs'); - } - return new Buffer(arg.serializeBinary()); -} - -function deserialize_DivArgs(buffer_arg) { - return math_math_pb.DivArgs.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_DivReply(arg) { - if (!(arg instanceof math_math_pb.DivReply)) { - throw new Error('Expected argument of type DivReply'); - } - return new Buffer(arg.serializeBinary()); -} - -function deserialize_DivReply(buffer_arg) { - return math_math_pb.DivReply.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_FibArgs(arg) { - if (!(arg instanceof math_math_pb.FibArgs)) { - throw new Error('Expected argument of type FibArgs'); - } - return new Buffer(arg.serializeBinary()); -} - -function deserialize_FibArgs(buffer_arg) { - return math_math_pb.FibArgs.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_Num(arg) { - if (!(arg instanceof math_math_pb.Num)) { - throw new Error('Expected argument of type Num'); - } - return new Buffer(arg.serializeBinary()); -} - -function deserialize_Num(buffer_arg) { - return math_math_pb.Num.deserializeBinary(new Uint8Array(buffer_arg)); -} - - -var MathService = exports.MathService = { - // Div divides args.dividend by args.divisor and returns the quotient and - // remainder. - div: { - path: '/math.Math/Div', - requestStream: false, - responseStream: false, - requestType: math_math_pb.DivArgs, - responseType: math_math_pb.DivReply, - requestSerialize: serialize_DivArgs, - requestDeserialize: deserialize_DivArgs, - responseSerialize: serialize_DivReply, - responseDeserialize: deserialize_DivReply, - }, - // DivMany accepts an arbitrary number of division args from the client stream - // and sends back the results in the reply stream. The stream continues until - // the client closes its end; the server does the same after sending all the - // replies. The stream ends immediately if either end aborts. - divMany: { - path: '/math.Math/DivMany', - requestStream: true, - responseStream: true, - requestType: math_math_pb.DivArgs, - responseType: math_math_pb.DivReply, - requestSerialize: serialize_DivArgs, - requestDeserialize: deserialize_DivArgs, - responseSerialize: serialize_DivReply, - responseDeserialize: deserialize_DivReply, - }, - // Fib generates numbers in the Fibonacci sequence. If args.limit > 0, Fib - // generates up to limit numbers; otherwise it continues until the call is - // canceled. Unlike Fib above, Fib has no final FibReply. - fib: { - path: '/math.Math/Fib', - requestStream: false, - responseStream: true, - requestType: math_math_pb.FibArgs, - responseType: math_math_pb.Num, - requestSerialize: serialize_FibArgs, - requestDeserialize: deserialize_FibArgs, - responseSerialize: serialize_Num, - responseDeserialize: deserialize_Num, - }, - // Sum sums a stream of numbers, returning the final result once the stream - // is closed. - sum: { - path: '/math.Math/Sum', - requestStream: true, - responseStream: false, - requestType: math_math_pb.Num, - responseType: math_math_pb.Num, - requestSerialize: serialize_Num, - requestDeserialize: deserialize_Num, - responseSerialize: serialize_Num, - responseDeserialize: deserialize_Num, - }, -}; - -exports.MathClient = grpc.makeGenericClientConstructor(MathService); diff --git a/test/api/math/math_pb.js b/test/api/math/math_pb.js deleted file mode 100644 index ccc05c6e0..000000000 --- a/test/api/math/math_pb.js +++ /dev/null @@ -1,866 +0,0 @@ -/** - * @fileoverview - * @enhanceable - * @public - */ -// GENERATED CODE -- DO NOT EDIT! - -var jspb = require('google-protobuf'); -var goog = jspb; -var global = Function('return this')(); - -goog.exportSymbol('proto.math.DivArgs', null, global); -goog.exportSymbol('proto.math.DivReply', null, global); -goog.exportSymbol('proto.math.FibArgs', null, global); -goog.exportSymbol('proto.math.FibReply', null, global); -goog.exportSymbol('proto.math.Num', null, global); - -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.math.DivArgs = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.math.DivArgs, jspb.Message); -if (goog.DEBUG && !COMPILED) { - proto.math.DivArgs.displayName = 'proto.math.DivArgs'; -} - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto suitable for use in Soy templates. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. - * @param {boolean=} opt_includeInstance Whether to include the JSPB instance - * for transitional soy proto support: http://goto/soy-param-migration - * @return {!Object} - */ -proto.math.DivArgs.prototype.toObject = function(opt_includeInstance) { - return proto.math.DivArgs.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Whether to include the JSPB - * instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.math.DivArgs} msg The msg instance to transform. - * @return {!Object} - */ -proto.math.DivArgs.toObject = function(includeInstance, msg) { - var f, obj = { - dividend: msg.getDividend(), - divisor: msg.getDivisor() - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.math.DivArgs} - */ -proto.math.DivArgs.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.math.DivArgs; - return proto.math.DivArgs.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.math.DivArgs} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.math.DivArgs} - */ -proto.math.DivArgs.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {number} */ (reader.readInt64()); - msg.setDividend(value); - break; - case 2: - var value = /** @type {number} */ (reader.readInt64()); - msg.setDivisor(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Class method variant: serializes the given message to binary data - * (in protobuf wire format), writing to the given BinaryWriter. - * @param {!proto.math.DivArgs} message - * @param {!jspb.BinaryWriter} writer - */ -proto.math.DivArgs.serializeBinaryToWriter = function(message, writer) { - message.serializeBinaryToWriter(writer); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.math.DivArgs.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - this.serializeBinaryToWriter(writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format), - * writing to the given BinaryWriter. - * @param {!jspb.BinaryWriter} writer - */ -proto.math.DivArgs.prototype.serializeBinaryToWriter = function (writer) { - var f = undefined; - f = this.getDividend(); - if (f !== 0) { - writer.writeInt64( - 1, - f - ); - } - f = this.getDivisor(); - if (f !== 0) { - writer.writeInt64( - 2, - f - ); - } -}; - - -/** - * Creates a deep clone of this proto. No data is shared with the original. - * @return {!proto.math.DivArgs} The clone. - */ -proto.math.DivArgs.prototype.cloneMessage = function() { - return /** @type {!proto.math.DivArgs} */ (jspb.Message.cloneMessage(this)); -}; - - -/** - * optional int64 dividend = 1; - * @return {number} - */ -proto.math.DivArgs.prototype.getDividend = function() { - return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0)); -}; - - -/** @param {number} value */ -proto.math.DivArgs.prototype.setDividend = function(value) { - jspb.Message.setField(this, 1, value); -}; - - -/** - * optional int64 divisor = 2; - * @return {number} - */ -proto.math.DivArgs.prototype.getDivisor = function() { - return /** @type {number} */ (jspb.Message.getFieldProto3(this, 2, 0)); -}; - - -/** @param {number} value */ -proto.math.DivArgs.prototype.setDivisor = function(value) { - jspb.Message.setField(this, 2, value); -}; - - - -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.math.DivReply = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.math.DivReply, jspb.Message); -if (goog.DEBUG && !COMPILED) { - proto.math.DivReply.displayName = 'proto.math.DivReply'; -} - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto suitable for use in Soy templates. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. - * @param {boolean=} opt_includeInstance Whether to include the JSPB instance - * for transitional soy proto support: http://goto/soy-param-migration - * @return {!Object} - */ -proto.math.DivReply.prototype.toObject = function(opt_includeInstance) { - return proto.math.DivReply.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Whether to include the JSPB - * instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.math.DivReply} msg The msg instance to transform. - * @return {!Object} - */ -proto.math.DivReply.toObject = function(includeInstance, msg) { - var f, obj = { - quotient: msg.getQuotient(), - remainder: msg.getRemainder() - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.math.DivReply} - */ -proto.math.DivReply.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.math.DivReply; - return proto.math.DivReply.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.math.DivReply} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.math.DivReply} - */ -proto.math.DivReply.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {number} */ (reader.readInt64()); - msg.setQuotient(value); - break; - case 2: - var value = /** @type {number} */ (reader.readInt64()); - msg.setRemainder(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Class method variant: serializes the given message to binary data - * (in protobuf wire format), writing to the given BinaryWriter. - * @param {!proto.math.DivReply} message - * @param {!jspb.BinaryWriter} writer - */ -proto.math.DivReply.serializeBinaryToWriter = function(message, writer) { - message.serializeBinaryToWriter(writer); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.math.DivReply.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - this.serializeBinaryToWriter(writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format), - * writing to the given BinaryWriter. - * @param {!jspb.BinaryWriter} writer - */ -proto.math.DivReply.prototype.serializeBinaryToWriter = function (writer) { - var f = undefined; - f = this.getQuotient(); - if (f !== 0) { - writer.writeInt64( - 1, - f - ); - } - f = this.getRemainder(); - if (f !== 0) { - writer.writeInt64( - 2, - f - ); - } -}; - - -/** - * Creates a deep clone of this proto. No data is shared with the original. - * @return {!proto.math.DivReply} The clone. - */ -proto.math.DivReply.prototype.cloneMessage = function() { - return /** @type {!proto.math.DivReply} */ (jspb.Message.cloneMessage(this)); -}; - - -/** - * optional int64 quotient = 1; - * @return {number} - */ -proto.math.DivReply.prototype.getQuotient = function() { - return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0)); -}; - - -/** @param {number} value */ -proto.math.DivReply.prototype.setQuotient = function(value) { - jspb.Message.setField(this, 1, value); -}; - - -/** - * optional int64 remainder = 2; - * @return {number} - */ -proto.math.DivReply.prototype.getRemainder = function() { - return /** @type {number} */ (jspb.Message.getFieldProto3(this, 2, 0)); -}; - - -/** @param {number} value */ -proto.math.DivReply.prototype.setRemainder = function(value) { - jspb.Message.setField(this, 2, value); -}; - - - -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.math.FibArgs = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.math.FibArgs, jspb.Message); -if (goog.DEBUG && !COMPILED) { - proto.math.FibArgs.displayName = 'proto.math.FibArgs'; -} - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto suitable for use in Soy templates. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. - * @param {boolean=} opt_includeInstance Whether to include the JSPB instance - * for transitional soy proto support: http://goto/soy-param-migration - * @return {!Object} - */ -proto.math.FibArgs.prototype.toObject = function(opt_includeInstance) { - return proto.math.FibArgs.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Whether to include the JSPB - * instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.math.FibArgs} msg The msg instance to transform. - * @return {!Object} - */ -proto.math.FibArgs.toObject = function(includeInstance, msg) { - var f, obj = { - limit: msg.getLimit() - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.math.FibArgs} - */ -proto.math.FibArgs.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.math.FibArgs; - return proto.math.FibArgs.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.math.FibArgs} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.math.FibArgs} - */ -proto.math.FibArgs.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {number} */ (reader.readInt64()); - msg.setLimit(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Class method variant: serializes the given message to binary data - * (in protobuf wire format), writing to the given BinaryWriter. - * @param {!proto.math.FibArgs} message - * @param {!jspb.BinaryWriter} writer - */ -proto.math.FibArgs.serializeBinaryToWriter = function(message, writer) { - message.serializeBinaryToWriter(writer); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.math.FibArgs.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - this.serializeBinaryToWriter(writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format), - * writing to the given BinaryWriter. - * @param {!jspb.BinaryWriter} writer - */ -proto.math.FibArgs.prototype.serializeBinaryToWriter = function (writer) { - var f = undefined; - f = this.getLimit(); - if (f !== 0) { - writer.writeInt64( - 1, - f - ); - } -}; - - -/** - * Creates a deep clone of this proto. No data is shared with the original. - * @return {!proto.math.FibArgs} The clone. - */ -proto.math.FibArgs.prototype.cloneMessage = function() { - return /** @type {!proto.math.FibArgs} */ (jspb.Message.cloneMessage(this)); -}; - - -/** - * optional int64 limit = 1; - * @return {number} - */ -proto.math.FibArgs.prototype.getLimit = function() { - return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0)); -}; - - -/** @param {number} value */ -proto.math.FibArgs.prototype.setLimit = function(value) { - jspb.Message.setField(this, 1, value); -}; - - - -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.math.Num = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.math.Num, jspb.Message); -if (goog.DEBUG && !COMPILED) { - proto.math.Num.displayName = 'proto.math.Num'; -} - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto suitable for use in Soy templates. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. - * @param {boolean=} opt_includeInstance Whether to include the JSPB instance - * for transitional soy proto support: http://goto/soy-param-migration - * @return {!Object} - */ -proto.math.Num.prototype.toObject = function(opt_includeInstance) { - return proto.math.Num.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Whether to include the JSPB - * instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.math.Num} msg The msg instance to transform. - * @return {!Object} - */ -proto.math.Num.toObject = function(includeInstance, msg) { - var f, obj = { - num: msg.getNum() - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.math.Num} - */ -proto.math.Num.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.math.Num; - return proto.math.Num.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.math.Num} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.math.Num} - */ -proto.math.Num.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {number} */ (reader.readInt64()); - msg.setNum(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Class method variant: serializes the given message to binary data - * (in protobuf wire format), writing to the given BinaryWriter. - * @param {!proto.math.Num} message - * @param {!jspb.BinaryWriter} writer - */ -proto.math.Num.serializeBinaryToWriter = function(message, writer) { - message.serializeBinaryToWriter(writer); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.math.Num.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - this.serializeBinaryToWriter(writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format), - * writing to the given BinaryWriter. - * @param {!jspb.BinaryWriter} writer - */ -proto.math.Num.prototype.serializeBinaryToWriter = function (writer) { - var f = undefined; - f = this.getNum(); - if (f !== 0) { - writer.writeInt64( - 1, - f - ); - } -}; - - -/** - * Creates a deep clone of this proto. No data is shared with the original. - * @return {!proto.math.Num} The clone. - */ -proto.math.Num.prototype.cloneMessage = function() { - return /** @type {!proto.math.Num} */ (jspb.Message.cloneMessage(this)); -}; - - -/** - * optional int64 num = 1; - * @return {number} - */ -proto.math.Num.prototype.getNum = function() { - return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0)); -}; - - -/** @param {number} value */ -proto.math.Num.prototype.setNum = function(value) { - jspb.Message.setField(this, 1, value); -}; - - - -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.math.FibReply = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.math.FibReply, jspb.Message); -if (goog.DEBUG && !COMPILED) { - proto.math.FibReply.displayName = 'proto.math.FibReply'; -} - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto suitable for use in Soy templates. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. - * @param {boolean=} opt_includeInstance Whether to include the JSPB instance - * for transitional soy proto support: http://goto/soy-param-migration - * @return {!Object} - */ -proto.math.FibReply.prototype.toObject = function(opt_includeInstance) { - return proto.math.FibReply.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Whether to include the JSPB - * instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.math.FibReply} msg The msg instance to transform. - * @return {!Object} - */ -proto.math.FibReply.toObject = function(includeInstance, msg) { - var f, obj = { - count: msg.getCount() - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.math.FibReply} - */ -proto.math.FibReply.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.math.FibReply; - return proto.math.FibReply.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.math.FibReply} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.math.FibReply} - */ -proto.math.FibReply.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {number} */ (reader.readInt64()); - msg.setCount(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Class method variant: serializes the given message to binary data - * (in protobuf wire format), writing to the given BinaryWriter. - * @param {!proto.math.FibReply} message - * @param {!jspb.BinaryWriter} writer - */ -proto.math.FibReply.serializeBinaryToWriter = function(message, writer) { - message.serializeBinaryToWriter(writer); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.math.FibReply.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - this.serializeBinaryToWriter(writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the message to binary data (in protobuf wire format), - * writing to the given BinaryWriter. - * @param {!jspb.BinaryWriter} writer - */ -proto.math.FibReply.prototype.serializeBinaryToWriter = function (writer) { - var f = undefined; - f = this.getCount(); - if (f !== 0) { - writer.writeInt64( - 1, - f - ); - } -}; - - -/** - * Creates a deep clone of this proto. No data is shared with the original. - * @return {!proto.math.FibReply} The clone. - */ -proto.math.FibReply.prototype.cloneMessage = function() { - return /** @type {!proto.math.FibReply} */ (jspb.Message.cloneMessage(this)); -}; - - -/** - * optional int64 count = 1; - * @return {number} - */ -proto.math.FibReply.prototype.getCount = function() { - return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0)); -}; - - -/** @param {number} value */ -proto.math.FibReply.prototype.setCount = function(value) { - jspb.Message.setField(this, 1, value); -}; - - -goog.object.extend(exports, proto.math); diff --git a/test/api/math/math_server.js b/test/api/math/math_server.js deleted file mode 100644 index 05e30f722..000000000 --- a/test/api/math/math_server.js +++ /dev/null @@ -1,124 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var grpc = require('grpc'); -var grpcMath = require('./math_grpc_pb'); -var math = require('./math_pb'); - -/** - * Server function for division. Provides the /Math/DivMany and /Math/Div - * functions (Div is just DivMany with only one stream element). For each - * DivArgs parameter, responds with a DivReply with the results of the division - * @param {Object} call The object containing request and cancellation info - * @param {function(Error, *)} cb Response callback - */ -function mathDiv(call, cb) { - var req = call.request; - var divisor = req.getDivisor(); - var dividend = req.getDividend(); - // Unary + is explicit coersion to integer - if (req.getDivisor() === 0) { - cb(new Error('cannot divide by zero')); - } else { - var response = new math.DivReply(); - response.setQuotient(Math.floor(dividend / divisor)); - response.setRemainder(dividend % divisor); - cb(null, response); - } -} - -/** - * Server function for Fibonacci numbers. Provides the /Math/Fib function. Reads - * a single parameter that indicates the number of responses, and then responds - * with a stream of that many Fibonacci numbers. - * @param {stream} stream The stream for sending responses. - */ -function mathFib(stream) { - // Here, call is a standard writable Node object Stream - var previous = 0, current = 1; - for (var i = 0; i < stream.request.getLimit(); i++) { - var response = new math.Num(); - response.setNum(current); - stream.write(response); - var temp = current; - current += previous; - previous = temp; - } - stream.end(); -} - -/** - * Server function for summation. Provides the /Math/Sum function. Reads a - * stream of number parameters, then responds with their sum. - * @param {stream} call The stream of arguments. - * @param {function(Error, *)} cb Response callback - */ -function mathSum(call, cb) { - // Here, call is a standard readable Node object Stream - var sum = 0; - call.on('data', function(data) { - sum += data.getNum(); - }); - call.on('end', function() { - var response = new math.Num(); - response.setNum(sum); - cb(null, response); - }); -} - -function mathDivMany(stream) { - stream.on('data', function(div_args) { - var divisor = div_args.getDivisor(); - var dividend = div_args.getDividend(); - if (divisor === 0) { - stream.emit('error', new Error('cannot divide by zero')); - } else { - var response = new math.DivReply(); - response.setQuotient(Math.floor(dividend / divisor)); - response.setRemainder(dividend % divisor); - stream.write(response); - } - }); - stream.on('end', function() { - stream.end(); - }); -} - -function getMathServer() { - var server = new grpc.Server(); - server.addService(grpcMath.MathService, { - div: mathDiv, - fib: mathFib, - sum: mathSum, - divMany: mathDivMany - }); - return server; -} - -if (require.main === module) { - var server = getMathServer(); - server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure()); - server.start(); -} - -/** - * See docs for server - */ -module.exports = getMathServer; diff --git a/test/api/math_client_test.js b/test/api/math_client_test.js deleted file mode 100644 index 09265abf6..000000000 --- a/test/api/math_client_test.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); - -var grpc = require('grpc'); -var math = require('./math/math_pb'); -var MathClient = require('./math/math_grpc_pb').MathClient; - -/** - * Client to use to make requests to a running server. - */ -var math_client; - -/** - * Server to test against - */ -var getServer = require('./math/math_server.js'); - -var server = getServer(); - -describe('Math client', function() { - before(function(done) { - var port_num = server.bind('0.0.0.0:0', - grpc.ServerCredentials.createInsecure()); - server.start(); - math_client = new MathClient('localhost:' + port_num, - grpc.credentials.createInsecure()); - done(); - }); - after(function() { - server.forceShutdown(); - }); - it('should handle a single request', function(done) { - var arg = new math.DivArgs(); - arg.setDividend(7); - arg.setDivisor(4); - math_client.div(arg, function handleDivResult(err, value) { - assert.ifError(err); - assert.equal(value.getQuotient(), 1); - assert.equal(value.getRemainder(), 3); - done(); - }); - }); - it('should handle an error from a unary request', function(done) { - var arg = new math.DivArgs(); - arg.setDividend(7); - arg.setDivisor(0); - math_client.div(arg, function handleDivResult(err, value) { - assert(err); - done(); - }); - }); - it('should handle a server streaming request', function(done) { - var arg = new math.FibArgs(); - arg.setLimit(7); - var call = math_client.fib(arg); - var expected_results = [1, 1, 2, 3, 5, 8, 13]; - var next_expected = 0; - call.on('data', function checkResponse(value) { - assert.equal(value.getNum(), expected_results[next_expected]); - next_expected += 1; - }); - call.on('status', function checkStatus(status) { - assert.strictEqual(status.code, grpc.status.OK); - done(); - }); - }); - it('should handle a client streaming request', function(done) { - var call = math_client.sum(function handleSumResult(err, value) { - assert.ifError(err); - assert.equal(value.getNum(), 21); - }); - for (var i = 0; i < 7; i++) { - var arg = new math.Num(); - arg.setNum(i); - call.write(arg); - } - call.end(); - call.on('status', function checkStatus(status) { - assert.strictEqual(status.code, grpc.status.OK); - done(); - }); - }); - it('should handle a bidirectional streaming request', function(done) { - function checkResponse(index, value) { - assert.equal(value.getQuotient(), index); - assert.equal(value.getRemainder(), 1); - } - var call = math_client.divMany(); - var response_index = 0; - call.on('data', function(value) { - checkResponse(response_index, value); - response_index += 1; - }); - for (var i = 0; i < 7; i++) { - var arg = new math.DivArgs(); - arg.setDividend(2 * i + 1); - arg.setDivisor(2); - call.write(arg); - } - call.end(); - call.on('status', function checkStatus(status) { - assert.strictEqual(status.code, grpc.status.OK); - done(); - }); - }); - it('should handle an error from a bidi request', function(done) { - var call = math_client.divMany(); - call.on('data', function(value) { - assert.fail(value, undefined, 'Unexpected data response on failing call', - '!='); - }); - var arg = new math.DivArgs(); - arg.setDividend(7); - arg.setDivisor(0); - call.write(arg); - call.end(); - call.on('error', function checkStatus(status) { - done(); - }); - }); -}); diff --git a/test/api/metadata_test.js b/test/api/metadata_test.js deleted file mode 100644 index 5e24f7ac8..000000000 --- a/test/api/metadata_test.js +++ /dev/null @@ -1,178 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var Metadata = require('grpc').Metadata; - -var assert = require('assert'); - -describe('Metadata', function() { - var metadata; - beforeEach(function() { - metadata = new Metadata(); - }); - describe('#set', function() { - it('Only accepts string values for non "-bin" keys', function() { - assert.throws(function() { - metadata.set('key', new Buffer('value')); - }); - assert.doesNotThrow(function() { - metadata.set('key', 'value'); - }); - }); - it('Only accepts Buffer values for "-bin" keys', function() { - assert.throws(function() { - metadata.set('key-bin', 'value'); - }); - assert.doesNotThrow(function() { - metadata.set('key-bin', new Buffer('value')); - }); - }); - it('Rejects invalid keys', function() { - assert.throws(function() { - metadata.set('key$', 'value'); - }); - assert.throws(function() { - metadata.set('', 'value'); - }); - }); - it('Rejects values with non-ASCII characters', function() { - assert.throws(function() { - metadata.set('key', 'résumé'); - }); - }); - it('Saves values that can be retrieved', function() { - metadata.set('key', 'value'); - assert.deepEqual(metadata.get('key'), ['value']); - }); - it('Overwrites previous values', function() { - metadata.set('key', 'value1'); - metadata.set('key', 'value2'); - assert.deepEqual(metadata.get('key'), ['value2']); - }); - it('Normalizes keys', function() { - metadata.set('Key', 'value1'); - assert.deepEqual(metadata.get('key'), ['value1']); - metadata.set('KEY', 'value2'); - assert.deepEqual(metadata.get('key'), ['value2']); - }); - }); - describe('#add', function() { - it('Only accepts string values for non "-bin" keys', function() { - assert.throws(function() { - metadata.add('key', new Buffer('value')); - }); - assert.doesNotThrow(function() { - metadata.add('key', 'value'); - }); - }); - it('Only accepts Buffer values for "-bin" keys', function() { - assert.throws(function() { - metadata.add('key-bin', 'value'); - }); - assert.doesNotThrow(function() { - metadata.add('key-bin', new Buffer('value')); - }); - }); - it('Rejects invalid keys', function() { - assert.throws(function() { - metadata.add('key$', 'value'); - }); - assert.throws(function() { - metadata.add('', 'value'); - }); - }); - it('Saves values that can be retrieved', function() { - metadata.add('key', 'value'); - assert.deepEqual(metadata.get('key'), ['value']); - }); - it('Combines with previous values', function() { - metadata.add('key', 'value1'); - metadata.add('key', 'value2'); - assert.deepEqual(metadata.get('key'), ['value1', 'value2']); - }); - it('Normalizes keys', function() { - metadata.add('Key', 'value1'); - assert.deepEqual(metadata.get('key'), ['value1']); - metadata.add('KEY', 'value2'); - assert.deepEqual(metadata.get('key'), ['value1', 'value2']); - }); - }); - describe('#remove', function() { - it('clears values from a key', function() { - metadata.add('key', 'value'); - metadata.remove('key'); - assert.deepEqual(metadata.get('key'), []); - }); - it('Normalizes keys', function() { - metadata.add('key', 'value'); - metadata.remove('KEY'); - assert.deepEqual(metadata.get('key'), []); - }); - }); - describe('#get', function() { - beforeEach(function() { - metadata.add('key', 'value1'); - metadata.add('key', 'value2'); - metadata.add('key-bin', new Buffer('value')); - }); - it('gets all values associated with a key', function() { - assert.deepEqual(metadata.get('key'), ['value1', 'value2']); - }); - it('Normalizes keys', function() { - assert.deepEqual(metadata.get('KEY'), ['value1', 'value2']); - }); - it('returns an empty list for non-existent keys', function() { - assert.deepEqual(metadata.get('non-existent-key'), []); - }); - it('returns Buffers for "-bin" keys', function() { - assert(metadata.get('key-bin')[0] instanceof Buffer); - }); - }); - describe('#getMap', function() { - it('gets a map of keys to values', function() { - metadata.add('key1', 'value1'); - metadata.add('Key2', 'value2'); - metadata.add('KEY3', 'value3'); - assert.deepEqual(metadata.getMap(), - {key1: 'value1', - key2: 'value2', - key3: 'value3'}); - }); - }); - describe('#clone', function() { - it('retains values from the original', function() { - metadata.add('key', 'value'); - var copy = metadata.clone(); - assert.deepEqual(copy.get('key'), ['value']); - }); - it('Does not see newly added values', function() { - metadata.add('key', 'value1'); - var copy = metadata.clone(); - metadata.add('key', 'value2'); - assert.deepEqual(copy.get('key'), ['value1']); - }); - it('Does not add new values to the original', function() { - metadata.add('key', 'value1'); - var copy = metadata.clone(); - copy.add('key', 'value2'); - assert.deepEqual(metadata.get('key'), ['value1']); - }); - }); -}); diff --git a/test/api/numbers.txt b/test/api/numbers.txt deleted file mode 100644 index 4972919b4..000000000 --- a/test/api/numbers.txt +++ /dev/null @@ -1,496 +0,0 @@ -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 -1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 diff --git a/test/api/surface_test.js b/test/api/surface_test.js deleted file mode 100644 index b2072fdd8..000000000 --- a/test/api/surface_test.js +++ /dev/null @@ -1,1379 +0,0 @@ -/* - * - * 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. - * - */ - -'use strict'; - -var assert = require('assert'); -var _ = require('lodash'); - -var grpc = require('grpc'); - -var MathClient = grpc.load( - __dirname + '/../../packages/grpc-native-core/deps/grpc/src/proto/math/math.proto').math.Math; -var mathServiceAttrs = MathClient.service; - -/** - * This is used for testing functions with multiple asynchronous calls that - * can happen in different orders. This should be passed the number of async - * function invocations that can occur last, and each of those should call this - * function's return value - * @param {function()} done The function that should be called when a test is - * complete. - * @param {number} count The number of calls to the resulting function if the - * test passes. - * @return {function()} The function that should be called at the end of each - * sequence of asynchronous functions. - */ -function multiDone(done, count) { - return function() { - count -= 1; - if (count <= 0) { - done(); - } - }; -} - -var server_insecure_creds = grpc.ServerCredentials.createInsecure(); - -describe('File loader', function() { - it('Should load a proto file by default', function() { - assert.doesNotThrow(function() { - grpc.load(__dirname + '/test_service.proto'); - }); - }); - it('Should load a proto file with the proto format', function() { - assert.doesNotThrow(function() { - grpc.load(__dirname + '/test_service.proto', 'proto'); - }); - }); - it('Should load a json file with the json format', function() { - assert.doesNotThrow(function() { - grpc.load(__dirname + '/test_service.json', 'json'); - }); - }); -}); -describe('surface Server', function() { - var server; - beforeEach(function() { - server = new grpc.Server(); - }); - afterEach(function() { - server.forceShutdown(); - }); - it('should error if started twice', function() { - server.start(); - assert.throws(function() { - server.start(); - }); - }); - it('should error if a port is bound after the server starts', function() { - server.start(); - assert.throws(function() { - server.bind('localhost:0', grpc.ServerCredentials.createInsecure()); - }); - }); - it('should successfully shutdown if tryShutdown is called', function(done) { - server.start(); - server.tryShutdown(done); - }); -}); -describe('Server.prototype.addService', function() { - var server; - var dummyImpls = { - 'div': function() {}, - 'divMany': function() {}, - 'fib': function() {}, - 'sum': function() {} - }; - beforeEach(function() { - server = new grpc.Server(); - }); - afterEach(function() { - server.forceShutdown(); - }); - it('Should succeed with a single service', function() { - assert.doesNotThrow(function() { - server.addService(mathServiceAttrs, dummyImpls); - }); - }); - it('Should fail with conflicting method names', function() { - server.addService(mathServiceAttrs, dummyImpls); - assert.throws(function() { - server.addService(mathServiceAttrs, dummyImpls); - }); - }); - it('Should allow method names as originally written', function() { - var altDummyImpls = { - 'Div': function() {}, - 'DivMany': function() {}, - 'Fib': function() {}, - 'Sum': function() {} - }; - assert.doesNotThrow(function() { - server.addService(mathServiceAttrs, altDummyImpls); - }); - }); - it('Should have a conflict between name variations', function() { - /* This is really testing that both name variations are actually used, - by checking that the method actually gets registered, for the - corresponding function, in both cases */ - var altDummyImpls = { - 'Div': function() {}, - 'DivMany': function() {}, - 'Fib': function() {}, - 'Sum': function() {} - }; - server.addProtoService(mathServiceAttrs, altDummyImpls); - assert.throws(function() { - server.addProtoService(mathServiceAttrs, dummyImpls); - }); - }); - it('Should fail if the server has been started', function() { - server.start(); - assert.throws(function() { - server.addService(mathServiceAttrs, dummyImpls); - }); - }); - describe('Default handlers', function() { - var client; - beforeEach(function() { - server.addService(mathServiceAttrs, {}); - var port = server.bind('localhost:0', server_insecure_creds); - client = new MathClient('localhost:' + port, - grpc.credentials.createInsecure()); - server.start(); - }); - it('should respond to a unary call with UNIMPLEMENTED', function(done) { - client.div({divisor: 4, dividend: 3}, function(error, response) { - assert(error); - assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); - done(); - }); - }); - it('should respond to a client stream with UNIMPLEMENTED', function(done) { - var call = client.sum(function(error, respones) { - assert(error); - assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); - done(); - }); - call.end(); - }); - it('should respond to a server stream with UNIMPLEMENTED', function(done) { - var call = client.fib({limit: 5}); - call.on('data', function(value) { - assert.fail('No messages expected'); - }); - call.on('error', function(err) { - assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); - done(); - }); - call.on('error', function(status) { /* Do nothing */ }); - }); - it('should respond to a bidi call with UNIMPLEMENTED', function(done) { - var call = client.divMany(); - call.on('data', function(value) { - assert.fail('No messages expected'); - }); - call.on('error', function(err) { - assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); - done(); - }); - call.on('error', function(status) { /* Do nothing */ }); - call.end(); - }); - }); -}); -describe('Client constructor building', function() { - var illegal_service_attrs = { - $method : { - path: '/illegal/$method', - requestStream: false, - responseStream: false, - requestSerialize: _.identity, - requestDeserialize: _.identity, - responseSerialize: _.identity, - responseDeserialize: _.identity - } - }; - it('Should reject method names starting with $', function() { - assert.throws(function() { - grpc.makeGenericClientConstructor(illegal_service_attrs); - }, /\$/); - }); - it('Should add aliases for original names', function() { - var Client = grpc.makeGenericClientConstructor(mathServiceAttrs); - assert.strictEqual(Client.prototype.add, Client.prototype.Add); - }); -}); -describe('waitForClientReady', function() { - var server; - var port; - var Client = MathClient; - var client; - before(function() { - server = new grpc.Server(); - port = server.bind('localhost:0', grpc.ServerCredentials.createInsecure()); - server.start(); - }); - beforeEach(function() { - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - }); - after(function() { - server.forceShutdown(); - }); - it('should complete when called alone', function(done) { - grpc.waitForClientReady(client, Infinity, function(error) { - assert.ifError(error); - done(); - }); - }); - it('should complete when a call is initiated', function(done) { - grpc.waitForClientReady(client, Infinity, function(error) { - assert.ifError(error); - done(); - }); - var call = client.div({}, function(err, response) {}); - call.cancel(); - }); - it('should complete if called more than once', function(done) { - done = multiDone(done, 2); - grpc.waitForClientReady(client, Infinity, function(error) { - assert.ifError(error); - done(); - }); - grpc.waitForClientReady(client, Infinity, function(error) { - assert.ifError(error); - done(); - }); - }); - it('should complete if called when already ready', function(done) { - grpc.waitForClientReady(client, Infinity, function(error) { - assert.ifError(error); - grpc.waitForClientReady(client, Infinity, function(error) { - assert.ifError(error); - done(); - }); - }); - }); - it('should time out if the server does not exist', function(done) { - var bad_client = new Client('nonexistent_hostname', - grpc.credentials.createInsecure()); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - grpc.waitForClientReady(bad_client, deadline, function(error) { - assert(error); - done(); - }); - }); -}); -describe('Echo service', function() { - var server; - var client; - before(function() { - var Client = grpc.load(__dirname + '/echo_service.proto').EchoService; - server = new grpc.Server(); - server.addService(Client.service, { - echo: function(call, callback) { - callback(null, call.request); - } - }); - var port = server.bind('localhost:0', server_insecure_creds); - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - server.start(); - }); - after(function() { - server.forceShutdown(); - }); - it('should echo the recieved message directly', function(done) { - client.echo({value: 'test value', value2: 3}, function(error, response) { - assert.ifError(error); - assert.deepEqual(response, {value: 'test value', value2: 3}); - done(); - }); - }); - it('Should convert an undefined argument to default values', function(done) { - client.echo(undefined, function(error, response) { - assert.ifError(error); - assert.deepEqual(response, {value: '', value2: 0}); - done(); - }); - }); -}); -describe('Generic client and server', function() { - function toString(val) { - return val.toString(); - } - function toBuffer(str) { - return new Buffer(str); - } - var string_service_attrs = { - 'capitalize' : { - path: '/string/capitalize', - requestStream: false, - responseStream: false, - requestSerialize: toBuffer, - requestDeserialize: toString, - responseSerialize: toBuffer, - responseDeserialize: toString - } - }; - describe('String client and server', function() { - var client; - var server; - before(function() { - server = new grpc.Server(); - server.addService(string_service_attrs, { - capitalize: function(call, callback) { - callback(null, _.capitalize(call.request)); - } - }); - var port = server.bind('localhost:0', server_insecure_creds); - server.start(); - var Client = grpc.makeGenericClientConstructor(string_service_attrs); - client = new Client('localhost:' + port, - grpc.credentials.createInsecure()); - }); - after(function() { - server.forceShutdown(); - }); - it('Should respond with a capitalized string', function(done) { - client.capitalize('abc', function(err, response) { - assert.ifError(err); - assert.strictEqual(response, 'Abc'); - done(); - }); - }); - }); -}); -describe('Server-side getPeer', function() { - function toString(val) { - return val.toString(); - } - function toBuffer(str) { - return new Buffer(str); - } - var string_service_attrs = { - 'getPeer' : { - path: '/string/getPeer', - requestStream: false, - responseStream: false, - requestSerialize: toBuffer, - requestDeserialize: toString, - responseSerialize: toBuffer, - responseDeserialize: toString - } - }; - var client; - var server; - before(function() { - server = new grpc.Server(); - server.addService(string_service_attrs, { - getPeer: function(call, callback) { - try { - callback(null, call.getPeer()); - } catch (e) { - call.emit('error', e); - } - } - }); - var port = server.bind('localhost:0', server_insecure_creds); - server.start(); - var Client = grpc.makeGenericClientConstructor(string_service_attrs); - client = new Client('localhost:' + port, - grpc.credentials.createInsecure()); - }); - after(function() { - server.forceShutdown(); - }); - it('should respond with a string representing the client', function(done) { - client.getPeer('', function(err, response) { - assert.ifError(err); - // We don't expect a specific value, just that it worked without error - done(); - }); - }); -}); -describe('Echo metadata', function() { - var client; - var server; - var metadata; - before(function() { - var Client = grpc.load(__dirname + '/test_service.proto').TestService; - server = new grpc.Server(); - server.addService(Client.service, { - unary: function(call, cb) { - call.sendMetadata(call.metadata); - cb(null, {}); - }, - clientStream: function(stream, cb){ - stream.on('data', function(data) {}); - stream.on('end', function() { - stream.sendMetadata(stream.metadata); - cb(null, {}); - }); - }, - serverStream: function(stream) { - stream.sendMetadata(stream.metadata); - stream.end(); - }, - bidiStream: function(stream) { - stream.on('data', function(data) {}); - stream.on('end', function() { - stream.sendMetadata(stream.metadata); - stream.end(); - }); - } - }); - var port = server.bind('localhost:0', server_insecure_creds); - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - server.start(); - metadata = new grpc.Metadata(); - metadata.set('key', 'value'); - }); - after(function() { - server.forceShutdown(); - }); - it('with unary call', function(done) { - var call = client.unary({}, metadata, function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('key'), ['value']); - done(); - }); - }); - it('with client stream call', function(done) { - var call = client.clientStream(metadata, function(err, data) { - assert.ifError(err); - }); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('key'), ['value']); - done(); - }); - call.end(); - }); - it('with server stream call', function(done) { - var call = client.serverStream({}, metadata); - call.on('data', function() {}); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('key'), ['value']); - done(); - }); - }); - it('with bidi stream call', function(done) { - var call = client.bidiStream(metadata); - call.on('data', function() {}); - call.on('metadata', function(metadata) { - assert.deepEqual(metadata.get('key'), ['value']); - done(); - }); - call.end(); - }); - it('shows the correct user-agent string', function(done) { - var version = require('grpc/package.json').version; - var call = client.unary({}, metadata, - function(err, data) { assert.ifError(err); }); - call.on('metadata', function(metadata) { - assert(_.startsWith(metadata.get('user-agent')[0], - 'grpc-node/' + version)); - done(); - }); - }); - it('properly handles duplicate values', function(done) { - var dup_metadata = metadata.clone(); - dup_metadata.add('key', 'value2'); - var call = client.unary({}, dup_metadata, - function(err, data) {assert.ifError(err); }); - call.on('metadata', function(resp_metadata) { - // Two arrays are equal iff their symmetric difference is empty - assert.deepEqual(_.xor(dup_metadata.get('key'), resp_metadata.get('key')), - []); - done(); - }); - }); -}); -describe('Client malformed response handling', function() { - var server; - var client; - var badArg = new Buffer([0xFF]); - before(function() { - var Client = grpc.load(__dirname + '/test_service.proto').TestService; - var malformed_test_service = { - unary: { - path: '/TestService/Unary', - requestStream: false, - responseStream: false, - requestDeserialize: _.identity, - responseSerialize: _.identity - }, - clientStream: { - path: '/TestService/ClientStream', - requestStream: true, - responseStream: false, - requestDeserialize: _.identity, - responseSerialize: _.identity - }, - serverStream: { - path: '/TestService/ServerStream', - requestStream: false, - responseStream: true, - requestDeserialize: _.identity, - responseSerialize: _.identity - }, - bidiStream: { - path: '/TestService/BidiStream', - requestStream: true, - responseStream: true, - requestDeserialize: _.identity, - responseSerialize: _.identity - } - }; - server = new grpc.Server(); - server.addService(malformed_test_service, { - unary: function(call, cb) { - cb(null, badArg); - }, - clientStream: function(stream, cb) { - stream.on('data', function() {/* Ignore requests */}); - stream.on('end', function() { - cb(null, badArg); - }); - }, - serverStream: function(stream) { - stream.write(badArg); - stream.end(); - }, - bidiStream: function(stream) { - stream.on('data', function() { - // Ignore requests - stream.write(badArg); - }); - stream.on('end', function() { - stream.end(); - }); - } - }); - var port = server.bind('localhost:0', server_insecure_creds); - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - server.start(); - }); - after(function() { - server.forceShutdown(); - }); - it('should get an INTERNAL status with a unary call', function(done) { - client.unary({}, function(err, data) { - assert(err); - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - }); - it('should get an INTERNAL status with a client stream call', function(done) { - var call = client.clientStream(function(err, data) { - assert(err); - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - call.write({}); - call.end(); - }); - it('should get an INTERNAL status with a server stream call', function(done) { - var call = client.serverStream({}); - call.on('data', function(){}); - call.on('error', function(err) { - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - }); - it('should get an INTERNAL status with a bidi stream call', function(done) { - var call = client.bidiStream(); - call.on('data', function(){}); - call.on('error', function(err) { - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - call.write({}); - call.end(); - }); -}); -describe('Server serialization failure handling', function() { - function serializeFail(obj) { - throw new Error('Serialization failed'); - } - var client; - var server; - before(function() { - var Client = grpc.load(__dirname + '/test_service.proto').TestService; - var malformed_test_service = { - unary: { - path: '/TestService/Unary', - requestStream: false, - responseStream: false, - requestDeserialize: _.identity, - responseSerialize: serializeFail - }, - clientStream: { - path: '/TestService/ClientStream', - requestStream: true, - responseStream: false, - requestDeserialize: _.identity, - responseSerialize: serializeFail - }, - serverStream: { - path: '/TestService/ServerStream', - requestStream: false, - responseStream: true, - requestDeserialize: _.identity, - responseSerialize: serializeFail - }, - bidiStream: { - path: '/TestService/BidiStream', - requestStream: true, - responseStream: true, - requestDeserialize: _.identity, - responseSerialize: serializeFail - } - }; - server = new grpc.Server(); - server.addService(malformed_test_service, { - unary: function(call, cb) { - cb(null, {}); - }, - clientStream: function(stream, cb) { - stream.on('data', function() {/* Ignore requests */}); - stream.on('end', function() { - cb(null, {}); - }); - }, - serverStream: function(stream) { - stream.write({}); - stream.end(); - }, - bidiStream: function(stream) { - stream.on('data', function() { - // Ignore requests - stream.write({}); - }); - stream.on('end', function() { - stream.end(); - }); - } - }); - var port = server.bind('localhost:0', server_insecure_creds); - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - server.start(); - }); - after(function() { - server.forceShutdown(); - }); - it('should get an INTERNAL status with a unary call', function(done) { - client.unary({}, function(err, data) { - assert(err); - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - }); - it('should get an INTERNAL status with a client stream call', function(done) { - var call = client.clientStream(function(err, data) { - assert(err); - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - call.write({}); - call.end(); - }); - it('should get an INTERNAL status with a server stream call', function(done) { - var call = client.serverStream({}); - call.on('data', function(){}); - call.on('error', function(err) { - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - }); - it('should get an INTERNAL status with a bidi stream call', function(done) { - var call = client.bidiStream(); - call.on('data', function(){}); - call.on('error', function(err) { - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - call.write({}); - call.end(); - }); -}); -describe('Other conditions', function() { - var Client; - var client; - var server; - var port; - before(function() { - Client = grpc.load(__dirname + '/test_service.proto').TestService; - server = new grpc.Server(); - var trailer_metadata = new grpc.Metadata(); - trailer_metadata.add('trailer-present', 'yes'); - server.addService(Client.service, { - unary: function(call, cb) { - var req = call.request; - if (req.error) { - cb({code: grpc.status.UNKNOWN, - details: 'Requested error'}, null, trailer_metadata); - } else { - cb(null, {count: 1}, trailer_metadata); - } - }, - clientStream: function(stream, cb){ - var count = 0; - var errored; - stream.on('data', function(data) { - if (data.error) { - errored = true; - cb(new Error('Requested error'), null, trailer_metadata); - } else { - count += 1; - } - }); - stream.on('end', function() { - if (!errored) { - cb(null, {count: count}, trailer_metadata); - } - }); - }, - serverStream: function(stream) { - var req = stream.request; - if (req.error) { - var err = {code: grpc.status.UNKNOWN, - details: 'Requested error'}; - err.metadata = trailer_metadata; - stream.emit('error', err); - } else { - for (var i = 0; i < 5; i++) { - stream.write({count: i}); - } - stream.end(trailer_metadata); - } - }, - bidiStream: function(stream) { - var count = 0; - stream.on('data', function(data) { - if (data.error) { - var err = new Error('Requested error'); - err.metadata = trailer_metadata.clone(); - err.metadata.add('count', '' + count); - stream.emit('error', err); - } else { - stream.write({count: count}); - count += 1; - } - }); - stream.on('end', function() { - stream.end(trailer_metadata); - }); - } - }); - port = server.bind('localhost:0', server_insecure_creds); - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - server.start(); - }); - after(function() { - server.forceShutdown(); - }); - it('channel.getTarget should be available', function() { - assert.strictEqual(typeof grpc.getClientChannel(client).getTarget(), - 'string'); - }); - it('client should be able to pause and resume a stream', function(done) { - var call = client.bidiStream(); - call.on('data', function(data) { - assert(data.count < 3); - call.pause(); - setTimeout(function() { - call.resume(); - }, 10); - }); - call.on('end', function() { - done(); - }); - call.write({}); - call.write({}); - call.write({}); - call.end(); - }); - describe('Server recieving bad input', function() { - var misbehavingClient; - var badArg = new Buffer([0xFF]); - before(function() { - var test_service_attrs = { - unary: { - path: '/TestService/Unary', - requestStream: false, - responseStream: false, - requestSerialize: _.identity, - responseDeserialize: _.identity - }, - clientStream: { - path: '/TestService/ClientStream', - requestStream: true, - responseStream: false, - requestSerialize: _.identity, - responseDeserialize: _.identity - }, - serverStream: { - path: '/TestService/ServerStream', - requestStream: false, - responseStream: true, - requestSerialize: _.identity, - responseDeserialize: _.identity - }, - bidiStream: { - path: '/TestService/BidiStream', - requestStream: true, - responseStream: true, - requestSerialize: _.identity, - responseDeserialize: _.identity - } - }; - var Client = grpc.makeGenericClientConstructor(test_service_attrs, - 'TestService'); - misbehavingClient = new Client('localhost:' + port, - grpc.credentials.createInsecure()); - }); - it('should respond correctly to a unary call', function(done) { - misbehavingClient.unary(badArg, function(err, data) { - assert(err); - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - }); - it('should respond correctly to a client stream', function(done) { - var call = misbehavingClient.clientStream(function(err, data) { - assert(err); - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - call.write(badArg); - // TODO(mlumish): Remove call.end() - call.end(); - }); - it('should respond correctly to a server stream', function(done) { - var call = misbehavingClient.serverStream(badArg); - call.on('data', function(data) { - assert.fail(data, null, 'Unexpected data', '==='); - }); - call.on('error', function(err) { - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - }); - it('should respond correctly to a bidi stream', function(done) { - var call = misbehavingClient.bidiStream(); - call.on('data', function(data) { - assert.fail(data, null, 'Unexpected data', '==='); - }); - call.on('error', function(err) { - assert.strictEqual(err.code, grpc.status.INTERNAL); - done(); - }); - call.write(badArg); - // TODO(mlumish): Remove call.end() - call.end(); - }); - }); - describe('Trailing metadata', function() { - it('should be present when a unary call succeeds', function(done) { - var call = client.unary({error: false}, function(err, data) { - assert.ifError(err); - }); - call.on('status', function(status) { - assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); - done(); - }); - }); - it('should be present when a unary call fails', function(done) { - var call = client.unary({error: true}, function(err, data) { - assert(err); - }); - call.on('status', function(status) { - assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); - done(); - }); - }); - it('should be present when a client stream call succeeds', function(done) { - var call = client.clientStream(function(err, data) { - assert.ifError(err); - }); - call.write({error: false}); - call.write({error: false}); - call.end(); - call.on('status', function(status) { - assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); - done(); - }); - }); - it('should be present when a client stream call fails', function(done) { - var call = client.clientStream(function(err, data) { - assert(err); - }); - call.write({error: false}); - call.write({error: true}); - call.end(); - call.on('status', function(status) { - assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); - done(); - }); - }); - it('should be present when a server stream call succeeds', function(done) { - var call = client.serverStream({error: false}); - call.on('data', function(){}); - call.on('status', function(status) { - assert.strictEqual(status.code, grpc.status.OK); - assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); - done(); - }); - }); - it('should be present when a server stream call fails', function(done) { - var call = client.serverStream({error: true}); - call.on('data', function(){}); - call.on('error', function(error) { - assert.deepEqual(error.metadata.get('trailer-present'), ['yes']); - done(); - }); - }); - it('should be present when a bidi stream succeeds', function(done) { - var call = client.bidiStream(); - call.write({error: false}); - call.write({error: false}); - call.end(); - call.on('data', function(){}); - call.on('status', function(status) { - assert.strictEqual(status.code, grpc.status.OK); - assert.deepEqual(status.metadata.get('trailer-present'), ['yes']); - done(); - }); - }); - it('should be present when a bidi stream fails', function(done) { - var call = client.bidiStream(); - call.write({error: false}); - call.write({error: true}); - call.end(); - call.on('data', function(){}); - call.on('error', function(error) { - assert.deepEqual(error.metadata.get('trailer-present'), ['yes']); - done(); - }); - }); - }); - describe('Error object should contain the status', function() { - it('for a unary call', function(done) { - client.unary({error: true}, function(err, data) { - assert(err); - assert.strictEqual(err.code, grpc.status.UNKNOWN); - assert.strictEqual(err.message, 'Requested error'); - done(); - }); - }); - it('for a client stream call', function(done) { - var call = client.clientStream(function(err, data) { - assert(err); - assert.strictEqual(err.code, grpc.status.UNKNOWN); - assert.strictEqual(err.message, 'Requested error'); - done(); - }); - call.write({error: false}); - call.write({error: true}); - call.end(); - }); - it('for a server stream call', function(done) { - var call = client.serverStream({error: true}); - call.on('data', function(){}); - call.on('error', function(error) { - assert.strictEqual(error.code, grpc.status.UNKNOWN); - assert.strictEqual(error.message, 'Requested error'); - done(); - }); - }); - it('for a bidi stream call', function(done) { - var call = client.bidiStream(); - call.write({error: false}); - call.write({error: true}); - call.end(); - call.on('data', function(){}); - call.on('error', function(error) { - assert.strictEqual(error.code, grpc.status.UNKNOWN); - assert.strictEqual(error.message, 'Requested error'); - done(); - }); - }); - }); - describe('call.getPeer should return the peer', function() { - it('for a unary call', function(done) { - var call = client.unary({error: false}, function(err, data) { - assert.ifError(err); - done(); - }); - assert.strictEqual(typeof call.getPeer(), 'string'); - }); - it('for a client stream call', function(done) { - var call = client.clientStream(function(err, data) { - assert.ifError(err); - done(); - }); - assert.strictEqual(typeof call.getPeer(), 'string'); - call.write({error: false}); - call.end(); - }); - it('for a server stream call', function(done) { - var call = client.serverStream({error: false}); - assert.strictEqual(typeof call.getPeer(), 'string'); - call.on('data', function(){}); - call.on('status', function(status) { - assert.strictEqual(status.code, grpc.status.OK); - done(); - }); - }); - it('for a bidi stream call', function(done) { - var call = client.bidiStream(); - assert.strictEqual(typeof call.getPeer(), 'string'); - call.write({error: false}); - call.end(); - call.on('data', function(){}); - call.on('status', function(status) { - done(); - }); - }); - it('after the call has fully completed', function(done) { - var peer; - var call = client.unary({error: false}, function(err, data) { - assert.ifError(err); - setImmediate(function() { - assert.strictEqual(peer, call.getPeer()); - done(); - }); - }); - peer = call.getPeer(); - assert.strictEqual(typeof peer, 'string'); - }); - }); -}); -describe('Call propagation', function() { - var proxy; - var proxy_impl; - - var Client; - var client; - var server; - before(function() { - Client = grpc.load(__dirname + '/test_service.proto').TestService; - server = new grpc.Server(); - server.addService(Client.service, { - unary: function(call) {}, - clientStream: function(stream) {}, - serverStream: function(stream) {}, - bidiStream: function(stream) {} - }); - var port = server.bind('localhost:0', server_insecure_creds); - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - server.start(); - }); - after(function() { - server.forceShutdown(); - }); - beforeEach(function() { - proxy = new grpc.Server(); - proxy_impl = { - unary: function(call) {}, - clientStream: function(stream) {}, - serverStream: function(stream) {}, - bidiStream: function(stream) {} - }; - }); - afterEach(function() { - proxy.forceShutdown(); - }); - describe('Cancellation', function() { - it('With a unary call', function(done) { - done = multiDone(done, 2); - var call; - proxy_impl.unary = function(parent, callback) { - client.unary(parent.request, {parent: parent}, function(err, value) { - try { - assert(err); - assert.strictEqual(err.code, grpc.status.CANCELLED); - } finally { - callback(err, value); - done(); - } - }); - call.cancel(); - }; - proxy.addService(Client.service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.credentials.createInsecure()); - call = proxy_client.unary({}, function(err, value) { done(); }); - }); - it('With a client stream call', function(done) { - done = multiDone(done, 2); - var call; - proxy_impl.clientStream = function(parent, callback) { - client.clientStream({parent: parent}, function(err, value) { - try { - assert(err); - assert.strictEqual(err.code, grpc.status.CANCELLED); - } finally { - callback(err, value); - done(); - } - }); - call.cancel(); - }; - proxy.addService(Client.service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.credentials.createInsecure()); - call = proxy_client.clientStream(function(err, value) { done(); }); - }); - it('With a server stream call', function(done) { - done = multiDone(done, 2); - var call; - proxy_impl.serverStream = function(parent) { - var child = client.serverStream(parent.request, {parent: parent}); - child.on('data', function() {}); - child.on('error', function(err) { - assert(err); - assert.strictEqual(err.code, grpc.status.CANCELLED); - done(); - }); - call.cancel(); - }; - proxy.addService(Client.service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.credentials.createInsecure()); - call = proxy_client.serverStream({}); - call.on('data', function() {}); - call.on('error', function(err) { - done(); - }); - }); - it('With a bidi stream call', function(done) { - done = multiDone(done, 2); - var call; - proxy_impl.bidiStream = function(parent) { - var child = client.bidiStream({parent: parent}); - child.on('data', function() {}); - child.on('error', function(err) { - assert(err); - assert.strictEqual(err.code, grpc.status.CANCELLED); - done(); - }); - call.cancel(); - }; - proxy.addService(Client.service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.credentials.createInsecure()); - call = proxy_client.bidiStream(); - call.on('data', function() {}); - call.on('error', function(err) { - done(); - }); - }); - }); - describe('Deadline', function() { - /* jshint bitwise:false */ - var deadline_flags = (grpc.propagate.DEFAULTS & - ~grpc.propagate.CANCELLATION); - it('With a client stream call', function(done) { - done = multiDone(done, 2); - proxy_impl.clientStream = function(parent, callback) { - var options = {parent: parent, propagate_flags: deadline_flags}; - client.clientStream(options, function(err, value) { - try { - assert(err); - assert(err.code === grpc.status.DEADLINE_EXCEEDED || - err.code === grpc.status.INTERNAL); - } finally { - callback(err, value); - done(); - } - }); - }; - proxy.addService(Client.service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.credentials.createInsecure()); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - proxy_client.clientStream({deadline: deadline}, function(err, value) { - done(); - }); - }); - it('With a bidi stream call', function(done) { - done = multiDone(done, 2); - proxy_impl.bidiStream = function(parent) { - var child = client.bidiStream( - {parent: parent, propagate_flags: deadline_flags}); - child.on('data', function() {}); - child.on('error', function(err) { - assert(err); - assert(err.code === grpc.status.DEADLINE_EXCEEDED || - err.code === grpc.status.INTERNAL); - done(); - }); - }; - proxy.addService(Client.service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.credentials.createInsecure()); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - var call = proxy_client.bidiStream({deadline: deadline}); - call.on('data', function() {}); - call.on('error', function(err) { - done(); - }); - }); - }); -}); -describe('Cancelling surface client', function() { - var client; - var server; - before(function() { - server = new grpc.Server(); - server.addService(mathServiceAttrs, { - 'div': function(stream) {}, - 'divMany': function(stream) {}, - 'fib': function(stream) {}, - 'sum': function(stream) {} - }); - var port = server.bind('localhost:0', server_insecure_creds); - var Client = grpc.makeGenericClientConstructor(mathServiceAttrs); - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - server.start(); - }); - after(function() { - server.forceShutdown(); - }); - it('Should correctly cancel a unary call', function(done) { - var call = client.div({'divisor': 0, 'dividend': 0}, function(err, resp) { - assert.strictEqual(err.code, grpc.status.CANCELLED); - done(); - }); - call.cancel(); - }); - it('Should correctly cancel a client stream call', function(done) { - var call = client.sum(function(err, resp) { - assert.strictEqual(err.code, grpc.status.CANCELLED); - done(); - }); - call.cancel(); - }); - it('Should correctly cancel a server stream call', function(done) { - var call = client.fib({'limit': 5}); - call.on('data', function() {}); - call.on('error', function(error) { - assert.strictEqual(error.code, grpc.status.CANCELLED); - done(); - }); - call.cancel(); - }); - it('Should correctly cancel a bidi stream call', function(done) { - var call = client.divMany(); - call.on('data', function() {}); - call.on('error', function(error) { - assert.strictEqual(error.code, grpc.status.CANCELLED); - done(); - }); - call.cancel(); - }); - it('Should be idempotent', function(done) { - var call = client.div({'divisor': 0, 'dividend': 0}, function(err, resp) { - assert.strictEqual(err.code, grpc.status.CANCELLED); - // Call asynchronously to try cancelling after call is fully completed - setImmediate(function() { - assert.doesNotThrow(function() { - call.cancel(); - }); - done(); - }); - }); - call.cancel(); - }); -}); -describe('Client reconnect', function() { - var server; - var Client; - var client; - var port; - beforeEach(function() { - Client = grpc.load(__dirname + '/echo_service.proto').EchoService; - server = new grpc.Server(); - server.addService(Client.service, { - echo: function(call, callback) { - callback(null, call.request); - } - }); - port = server.bind('localhost:0', server_insecure_creds); - client = new Client('localhost:' + port, grpc.credentials.createInsecure()); - server.start(); - }); - afterEach(function() { - server.forceShutdown(); - }); - it('should reconnect after server restart', function(done) { - client.echo({value: 'test value', value2: 3}, function(error, response) { - assert.ifError(error); - assert.deepEqual(response, {value: 'test value', value2: 3}); - server.tryShutdown(function() { - server = new grpc.Server(); - server.addService(Client.service, { - echo: function(call, callback) { - callback(null, call.request); - } - }); - server.bind('localhost:' + port, server_insecure_creds); - server.start(); - - /* We create a new client, that will not throw an error if the server - * is not immediately available. Instead, it will wait for the server - * to be available, then the call will complete. Once this happens, the - * original client should be able to make a new call and connect to the - * restarted server without having the call fail due to connection - * errors. */ - var client2 = new Client('localhost:' + port, - grpc.credentials.createInsecure()); - client2.echo({value: 'test', value2: 3}, function(error, response) { - assert.ifError(error); - client.echo(undefined, function(error, response) { - if (error) { - console.log(error); - } - assert.ifError(error); - assert.deepEqual(response, {value: '', value2: 0}); - done(); - }); - }); - }); - }); - }); -}); diff --git a/test/api/test_service.json b/test/api/test_service.json deleted file mode 100644 index 6f952c6ad..000000000 --- a/test/api/test_service.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "package": null, - "messages": [ - { - "name": "Request", - "fields": [ - { - "rule": "optional", - "type": "bool", - "name": "error", - "id": 1 - } - ] - }, - { - "name": "Response", - "fields": [ - { - "rule": "optional", - "type": "int32", - "name": "count", - "id": 1 - } - ] - } - ], - "services": [ - { - "name": "TestService", - "options": {}, - "rpc": { - "Unary": { - "request": "Request", - "response": "Response", - "options": {} - }, - "ClientStream": { - "request": "Request", - "response": "Response", - "options": {} - }, - "ServerStream": { - "request": "Request", - "response": "Response", - "options": {} - }, - "BidiStream": { - "request": "Request", - "response": "Response", - "options": {} - } - } - } - ] -} \ No newline at end of file diff --git a/test/channelz/channelz_manual_test.js b/test/channelz/channelz_manual_test.js new file mode 100644 index 000000000..2f77df3cc --- /dev/null +++ b/test/channelz/channelz_manual_test.js @@ -0,0 +1,73 @@ +/* + * + * Copyright 2021 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. + * + */ + +'use strict'; + +require('../fixtures/js_js'); +const interopClient = require('../interop/interop_client'); +const interopServer = require('../interop/interop_server'); +const serverGrpc = require('../any_grpc').server; + +const hostOverride = 'foo.test.google.fr'; + +const testCases = [ + 'empty_unary', + 'large_unary', + 'client_streaming', + 'server_streaming', + 'ping_pong', + 'empty_stream', + 'cancel_after_begin', + 'cancel_after_first_response', + 'timeout_on_sleeping_server', + 'custom_metadata', + 'status_code_and_message', + 'special_status_message', + 'unimplemented_service', + 'unimplemented_method' +]; + +function getRandomTest() { + return testCases[(Math.random() * testCases.length) | 0]; +} + +let testCompleteCount = 0; + +interopServer.getServer('0', true, (error, result) => { + if (error) { + throw error; + } + const channelzServer = new serverGrpc.Server(); + channelzServer.bindAsync('localhost:0', serverGrpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + throw error; + } + console.log(`Serving channelz at port ${port}`); + serverGrpc.addAdminServicesToServer(channelzServer); + channelzServer.start(); + result.server.start(); + setInterval(() => { + interopClient.runTest(`localhost:${result.port}`, hostOverride, getRandomTest(), true, true, () => { + testCompleteCount += 1; + if (testCompleteCount % 100 === 0) { + console.log(`Completed ${testCompleteCount} tests`); + } + }); + }, 100); + }); +}) \ No newline at end of file diff --git a/test/client-libraries-integration/.gitignore b/test/client-libraries-integration/.gitignore new file mode 100644 index 000000000..611c25ccf --- /dev/null +++ b/test/client-libraries-integration/.gitignore @@ -0,0 +1 @@ +libs/nodejs-*/ \ No newline at end of file diff --git a/test/client-libraries-integration/init.sh b/test/client-libraries-integration/init.sh new file mode 100755 index 000000000..e4814535b --- /dev/null +++ b/test/client-libraries-integration/init.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2019 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. + +npm install + +for dir in $(node -p "require('./repositories.json').join('\n')"); do + pushd libs + if [ ! -d $dir ]; then + git clone https://github.com/googleapis/$dir + pushd $dir + npm install + popd + fi + popd + SMOKE_TEST_PROJECT=$GCLOUD_PROJECT node --require ./use-grpc-js.js $(npm bin)/_mocha --timeout 60000 libs/$dir/system-test/*.js +done diff --git a/test/client-libraries-integration/libs/.gitkeep b/test/client-libraries-integration/libs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/test/client-libraries-integration/package.json b/test/client-libraries-integration/package.json new file mode 100644 index 000000000..784714eeb --- /dev/null +++ b/test/client-libraries-integration/package.json @@ -0,0 +1,11 @@ +{ + "name": "grpc-client-libraries-integration", + "version": "0.0.1", + "description": "", + "dependencies": { + "dot-prop": "^5.2.0", + "google-proto-files": "^0.15.1", + "mocha": "^5.0.4", + "shimmer": "^1.2.0" + } +} diff --git a/test/client-libraries-integration/repositories.json b/test/client-libraries-integration/repositories.json new file mode 100644 index 000000000..3333f58bb --- /dev/null +++ b/test/client-libraries-integration/repositories.json @@ -0,0 +1,14 @@ +[ + "nodejs-bigtable", + "nodejs-datastore", + "nodejs-dlp", + "nodejs-firestore", + "nodejs-language", + "nodejs-logging", + "nodejs-monitoring", + "nodejs-pubsub", + "nodejs-spanner", + "nodejs-speech", + "nodejs-video-intelligence", + "nodejs-vision" +] \ No newline at end of file diff --git a/test/client-libraries-integration/use-grpc-js.js b/test/client-libraries-integration/use-grpc-js.js new file mode 100644 index 000000000..4f73f92dd --- /dev/null +++ b/test/client-libraries-integration/use-grpc-js.js @@ -0,0 +1,129 @@ +/* + * Copyright 2019 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. + * + */ + +require('source-map-support/register'); +const Module = require('module'); +const shimmer = require('shimmer'); + +const grpcPJson = require('../../packages/grpc-js/package'); +const grpcImpl = require('../../packages/grpc-js'); +const grpcProtobuf = require('../../packages/proto-loader'); + +if (!process.env.USE_GRPC_NATIVE) { + shimmer.wrap(Module, '_load', (moduleLoad) => { + return function Module_load(moduleName, parent) { + if (moduleName === 'grpc') { + // load grpc-js when grpc is requested. + return grpcImpl; + } else if (moduleName.startsWith('grpc/package')) { + // load grpc-js's package.json when grpc's package.json is requested. + return grpcPJson; + } else { + const result = moduleLoad.apply(this, arguments); + // monkeypatch google-gax and @google-cloud/common-grpc to avoid all + // references to grpc.load and grpc.loadObject, implementing functions + // on top of the new API for loading proto files. + if (moduleName === 'google-gax') { + if (!result.grpc.prototype.load.__wrapped) { + shimmer.wrap(result.grpc.prototype, 'load', (gaxLoad) => { + return function (filename, format, options) { + if (Array.isArray(filename)) { + options = filename[2]; + filename = filename[0]; + } + options = Object.assign({ + convertFieldsToCamelCase: true, // gax load uses hardcoded convertFieldsToCamelCase = true if it's not specified. + binaryAsBase64: false, // gRPC default option + longsAsStrings: true, // gRPC default option + enumsAsStrings: true, // gRPC default option + }, options); + const packageDef = grpcProtobuf.loadSync(filename.file, { + keepCase: !options.convertFieldsToCamelCase, + defaults: true, + bytes: options.binaryAsBase64 ? String : Buffer, + longs: options.longsAsString ? String : null, + enums: options.enumsAsStrings ? String : null, + oneofs: true, + include: [filename.root] + }); + return grpcImpl.loadPackageDefinition(packageDef); + } + }); + } + if (!result.grpc.prototype.loadProto.__wrapped) { + shimmer.wrap(result.grpc.prototype, 'loadProto', (gaxLoadProto) => { + const path = require('path'); + const googleProtoFilesDir = require('google-proto-files')('..'); + return function (protoPath, filename) { + // loadProto does not accept options, so the options passed + // here correspond to defaultGrpcOptions. + const packageDef = grpcProtobuf.loadSync(filename, { + keepCase: false, + defaults: true, + bytes: Buffer, + longs: String, + enums: String, + oneofs: true, + include: [protoPath, googleProtoFilesDir] + }); + return grpcImpl.loadPackageDefinition(packageDef); + }; + }); + } + } else if (moduleName === '@google-cloud/common-grpc') { + if (!result.Service.prototype.loadProtoFile_.__wrapped) { + shimmer.wrap(result.Service.prototype, 'loadProtoFile_', (commonLoad) => { + const dotProp = require('dot-prop'); + // loadProtoFile_ uses a module-scope cache of loaded proto + // objects which isn't referenced anywhere else + const protoObjectCache = {}; + + return function (protoConfig, config) { + if (typeof protoConfig === 'string') { + protoConfig = { path: protoConfig }; + } + + const protoObjectCacheKey = [ + config.protosDir, + protoConfig.path, + protoConfig.service, + ].join('$'); + + if (!protoObjectCache[protoObjectCacheKey]) { + const services = grpcProtobuf.loadSync(protoConfig.path, { + keepCase: false, // loadProtoFile_ uses hardcoded convertFieldsToCamelCase = true + defaults: true, + bytes: String, // loadProtoFile_ uses hardcoded binaryAsBase64 = true + longs: String, + enums: String, + oneofs: true, + include: [config.protosDir] + }); + const service = dotProp.get(services.google, protoConfig.service); + protoObjectCache[protoObjectCacheKey] = service; + } + + return protoObjectCache[protoObjectCacheKey]; + } + }); + } + } + return result; + } + }; + }); +} diff --git a/test/data/ca.pem b/test/data/ca.pem index 6c8511a73..49d39cd8e 100644 --- a/test/data/ca.pem +++ b/test/data/ca.pem @@ -1,15 +1,20 @@ -----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= +MIIDWjCCAkKgAwIBAgIUWrP0VvHcy+LP6UuYNtiL9gBhD5owDQYJKoZIhvcNAQEL +BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTIw +MDMxNzE4NTk1MVoXDTMwMDMxNTE4NTk1MVowVjELMAkGA1UEBhMCQVUxEzARBgNV +BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 +ZDEPMA0GA1UEAwwGdGVzdGNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAsGL0oXflF0LzoM+Bh+qUU9yhqzw2w8OOX5mu/iNCyUOBrqaHi7mGHx73GD01 +diNzCzvlcQqdNIH6NQSL7DTpBjca66jYT9u73vZe2MDrr1nVbuLvfu9850cdxiUO +Inv5xf8+sTHG0C+a+VAvMhsLiRjsq+lXKRJyk5zkbbsETybqpxoJ+K7CoSy3yc/k +QIY3TipwEtwkKP4hzyo6KiGd/DPexie4nBUInN3bS1BUeNZ5zeaIC2eg3bkeeW7c +qT55b+Yen6CxY0TEkzBK6AKt/WUialKMgT0wbTxRZO7kUCH3Sq6e/wXeFdJ+HvdV +LPlAg5TnMaNpRdQih/8nRFpsdwIDAQABoyAwHjAMBgNVHRMEBTADAQH/MA4GA1Ud +DwEB/wQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEAkTrKZjBrJXHps/HrjNCFPb5a +THuGPCSsepe1wkKdSp1h4HGRpLoCgcLysCJ5hZhRpHkRihhef+rFHEe60UePQO3S +CVTtdJB4CYWpcNyXOdqefrbJW5QNljxgi6Fhvs7JJkBqdXIkWXtFk2eRgOIP2Eo9 +/OHQHlYnwZFrk6sp4wPyR+A95S0toZBcyDVz7u+hOW0pGK3wviOe9lvRgj/H3Pwt +bewb0l+MhRig0/DVHamyVxrDRbqInU1/GTNCwcZkXKYFWSf92U+kIcTth24Q1gcw +eZiLl5FfrWokUNytFElXob0V0a5/kbhiLc3yWmvWqHTpqCALbVyF+rKJo2f5Kw== -----END CERTIFICATE----- diff --git a/test/data/server1.key b/test/data/server1.key index 143a5b876..086462992 100644 --- a/test/data/server1.key +++ b/test/data/server1.key @@ -1,16 +1,28 @@ -----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 +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDnE443EknxvxBq +6+hvn/t09hl8hx366EBYvZmVM/NC+7igXRAjiJiA/mIaCvL3MS0Iz5hBLxSGICU+ +WproA3GCIFITIwcf/ETyWj/5xpgZ4AKrLrjQmmX8mhwUajfF3UvwMJrCOVqPp67t +PtP+2kBXaqrXdvnvXR41FsIB8V7zIAuIZB6bHQhiGVlc1sgZYsE2EGG9WMmHtS86 +qkAOTjG2XyjmPTGAwhGDpYkYrpzp99IiDh4/Veai81hn0ssQkbry0XRD/Ig3jcHh +23WiriPNJ0JsbgXUSLKRPZObA9VgOLy2aXoN84IMaeK3yy+cwSYG/99w93fUZJte +MXwz4oYZAgMBAAECggEBAIVn2Ncai+4xbH0OLWckabwgyJ4IM9rDc0LIU368O1kU +koais8qP9dujAWgfoh3sGh/YGgKn96VnsZjKHlyMgF+r4TaDJn3k2rlAOWcurGlj +1qaVlsV4HiEzp7pxiDmHhWvp4672Bb6iBG+bsjCUOEk/n9o9KhZzIBluRhtxCmw5 +nw4Do7z00PTvN81260uPWSc04IrytvZUiAIx/5qxD72bij2xJ8t/I9GI8g4FtoVB +8pB6S/hJX1PZhh9VlU6Yk+TOfOVnbebG4W5138LkB835eqk3Zz0qsbc2euoi8Hxi +y1VGwQEmMQ63jXz4c6g+X55ifvUK9Jpn5E8pq+pMd7ECgYEA93lYq+Cr54K4ey5t +sWMa+ye5RqxjzgXj2Kqr55jb54VWG7wp2iGbg8FMlkQwzTJwebzDyCSatguEZLuB +gRGroRnsUOy9vBvhKPOch9bfKIl6qOgzMJB267fBVWx5ybnRbWN/I7RvMQf3k+9y +biCIVnxDLEEYyx7z85/5qxsXg/MCgYEA7wmWKtCTn032Hy9P8OL49T0X6Z8FlkDC +Rk42ygrc/MUbugq9RGUxcCxoImOG9JXUpEtUe31YDm2j+/nbvrjl6/bP2qWs0V7l +dTJl6dABP51pCw8+l4cWgBBX08Lkeen812AAFNrjmDCjX6rHjWHLJcpS18fnRRkP +V1d/AHWX7MMCgYEA6Gsw2guhp0Zf2GCcaNK5DlQab8OL4Hwrpttzo4kuTlwtqNKp +Q9H4al9qfF4Cr1TFya98+EVYf8yFRM3NLNjZpe3gwYf2EerlJj7VLcahw0KKzoN1 +QBENfwgPLRk5sDkx9VhSmcfl/diLroZdpAwtv3vo4nEoxeuGFbKTGx3Qkf0CgYEA +xyR+dcb05Ygm3w4klHQTowQ10s1H80iaUcZBgQuR1ghEtDbUPZHsoR5t1xCB02ys +DgAwLv1bChIvxvH/L6KM8ovZ2LekBX4AviWxoBxJnfz/EVau98B0b1auRN6eSC83 +FRuGldlSOW1z/nSh8ViizSYE5H5HX1qkXEippvFRE88CgYB3Bfu3YQY60ITWIShv +nNkdcbTT9eoP9suaRJjw92Ln+7ZpALYlQMKUZmJ/5uBmLs4RFwUTQruLOPL4yLTH +awADWUzs3IRr1fwn9E+zM8JVyKCnUEM3w4N5UZskGO2klashAd30hWO+knRv/y0r +uGIYs9Ek7YXlXIRVrzMwcsrt1w== -----END PRIVATE KEY----- diff --git a/test/data/server1.pem b/test/data/server1.pem index f3d43fcc5..88244f856 100644 --- a/test/data/server1.pem +++ b/test/data/server1.pem @@ -1,16 +1,22 @@ -----BEGIN CERTIFICATE----- -MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET -MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx -MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV -BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 -ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco -LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg -zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd -9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw -CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy -em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G -CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 -hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh -y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 +MIIDtDCCApygAwIBAgIUbJfTREJ6k6/+oInWhV1O1j3ZT0IwDQYJKoZIhvcNAQEL +BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTIw +MDMxODAzMTA0MloXDTMwMDMxNjAzMTA0MlowZTELMAkGA1UEBhMCVVMxETAPBgNV +BAgMCElsbGlub2lzMRAwDgYDVQQHDAdDaGljYWdvMRUwEwYDVQQKDAxFeGFtcGxl +LCBDby4xGjAYBgNVBAMMESoudGVzdC5nb29nbGUuY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA5xOONxJJ8b8Qauvob5/7dPYZfIcd+uhAWL2ZlTPz +Qvu4oF0QI4iYgP5iGgry9zEtCM+YQS8UhiAlPlqa6ANxgiBSEyMHH/xE8lo/+caY +GeACqy640Jpl/JocFGo3xd1L8DCawjlaj6eu7T7T/tpAV2qq13b5710eNRbCAfFe +8yALiGQemx0IYhlZXNbIGWLBNhBhvVjJh7UvOqpADk4xtl8o5j0xgMIRg6WJGK6c +6ffSIg4eP1XmovNYZ9LLEJG68tF0Q/yIN43B4dt1oq4jzSdCbG4F1EiykT2TmwPV +YDi8tml6DfOCDGnit8svnMEmBv/fcPd31GSbXjF8M+KGGQIDAQABo2swaTAJBgNV +HRMEAjAAMAsGA1UdDwQEAwIF4DBPBgNVHREESDBGghAqLnRlc3QuZ29vZ2xlLmZy +ghh3YXRlcnpvb2kudGVzdC5nb29nbGUuYmWCEioudGVzdC55b3V0dWJlLmNvbYcE +wKgBAzANBgkqhkiG9w0BAQsFAAOCAQEAS8hDQA8PSgipgAml7Q3/djwQ644ghWQv +C2Kb+r30RCY1EyKNhnQnIIh/OUbBZvh0M0iYsy6xqXgfDhCB93AA6j0i5cS8fkhH +Jl4RK0tSkGQ3YNY4NzXwQP/vmUgfkw8VBAZ4Y4GKxppdATjffIW+srbAmdDruIRM +wPeikgOoRrXf0LA1fi4TqxARzeRwenQpayNfGHTvVF9aJkl8HoaMunTAdG5pIVcr +9GKi/gEMpXUJbbVv3U5frX1Wo4CFo+rZWJ/LyCMeb0jciNLxSdMwj/E/ZuExlyeZ +gc9ctPjSMvgSyXEKv6Vwobleeg88V2ZgzenziORoWj4KszG/lbQZvg== -----END CERTIFICATE----- diff --git a/packages/grpc-native-core/ext/completion_queue.h b/test/distrib/distrib-test.js similarity index 64% rename from packages/grpc-native-core/ext/completion_queue.h rename to test/distrib/distrib-test.js index 3a56c3271..708c93eb8 100644 --- a/packages/grpc-native-core/ext/completion_queue.h +++ b/test/distrib/distrib-test.js @@ -1,6 +1,5 @@ /* - * - * Copyright 2016 gRPC authors. + * Copyright 2022 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +15,8 @@ * */ -#include -#include - -namespace grpc { -namespace node { - -grpc_completion_queue *GetCompletionQueue(); - -void CompletionQueueNext(); - -void CompletionQueueInit(v8::Local exports); +const grpcJs = require('@grpc/grpc-js'); -void CompletionQueueForcePoll(); +const grpcJsXds = require('@grpc/grpc-js-xds'); -} // namespace node -} // namespace grpc +const protoLoader = require('@grpc/proto-loader'); diff --git a/test/distrib/run-distrib-test.sh b/test/distrib/run-distrib-test.sh new file mode 100755 index 000000000..e2ed0853c --- /dev/null +++ b/test/distrib/run-distrib-test.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Copyright 2022 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. + +set -ex + +cd $(dirname $0) +base=$(pwd) + +cd ../../packages/grpc-js +npm pack +cd ../grpc-js-xds +npm pack +cd ../proto-loader +npm pack + +cd $base +npm install ../../packages/grpc-js/grpc-grpc-js-*.tgz +npm install ../../packages/grpc-js-xds/grpc-grpc-js-xds-*.tgz +npm install ../../packages/proto-loader/grpc-proto-loader-*.tgz + +node ./distrib-test.js diff --git a/test/fixtures/js_js.js b/test/fixtures/js_js.js new file mode 100644 index 000000000..bb70c450e --- /dev/null +++ b/test/fixtures/js_js.js @@ -0,0 +1,19 @@ +/* + * Copyright 2019 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. + * + */ + +global._server_implementation = 'js'; +global._client_implementation = 'js'; diff --git a/test/fixtures/js_native.js b/test/fixtures/js_native.js new file mode 100644 index 000000000..88ef5bf64 --- /dev/null +++ b/test/fixtures/js_native.js @@ -0,0 +1,19 @@ +/* + * Copyright 2019 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. + * + */ + +global._server_implementation = 'js'; +global._client_implementation = 'native'; \ No newline at end of file diff --git a/test/fixtures/native_js.js b/test/fixtures/native_js.js new file mode 100644 index 000000000..89262636a --- /dev/null +++ b/test/fixtures/native_js.js @@ -0,0 +1,19 @@ +/* + * Copyright 2019 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. + * + */ + +global._server_implementation = 'native'; +global._client_implementation = 'js'; diff --git a/test/fixtures/native_native.js b/test/fixtures/native_native.js new file mode 100644 index 000000000..eb6c0ba80 --- /dev/null +++ b/test/fixtures/native_native.js @@ -0,0 +1,19 @@ +/* + * Copyright 2019 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. + * + */ + +global._server_implementation = 'native'; +global._client_implementation = 'native'; \ No newline at end of file diff --git a/test/gulpfile.js b/test/gulpfile.js deleted file mode 100644 index 469f8d23f..000000000 --- a/test/gulpfile.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2017 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. - * - */ - -const _gulp = require('gulp'); -const help = require('gulp-help'); -const mocha = require('gulp-mocha'); -const execa = require('execa'); -const path = require('path'); -const del = require('del'); - -// gulp-help monkeypatches tasks to have an additional description parameter -const gulp = help(_gulp); - -const testDir = __dirname; -const apiTestDir = path.resolve(testDir, 'api'); - -gulp.task('internal.test.clean.links', 'Delete npm links', () => { - return del(path.resolve(testDir, 'node_modules/grpc')); -}); - -gulp.task('internal.test.install', 'Install test dependencies', () => { - return execa('npm', ['install'], {cwd: testDir, stdio: 'inherit'}); -}); - -gulp.task('internal.test.clean.all', 'Delete all files created by tasks', - ['internal.test.clean.links']); - -gulp.task('internal.test.link.add', 'Link local copies of grpc packages', () => { - return execa('npm', ['link', 'grpc'], {cwd: testDir, stdio: 'inherit'}); -}); - -gulp.task('internal.test.test', 'Run API-level tests', () => { - return gulp.src(`${apiTestDir}/*.js`).pipe(mocha({reporter: 'mocha-jenkins-reporter'})); -}); diff --git a/test/gulpfile.ts b/test/gulpfile.ts new file mode 100644 index 000000000..6d43d4472 --- /dev/null +++ b/test/gulpfile.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2019 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. + * + */ + +import * as gulp from 'gulp'; +import * as mocha from 'gulp-mocha'; +import * as execa from 'execa'; +import * as path from 'path'; +import * as del from 'del'; +import * as semver from 'semver'; + +const testDir = __dirname; +const apiTestDir = path.resolve(testDir, 'api'); + +/* The native library has some misbehavior in specific tests when running in + * Node 14 and above. */ +const NATIVE_SUPPORT_RANGE = '<14'; + +const runInstall = () => { + return execa('npm', ['install'], {cwd: testDir, stdio: 'inherit'}); +}; + +const runRebuild = () => execa('npm', ['rebuild', '--unsafe-perm'], {cwd: testDir, stdio: 'inherit'}); + +const install = gulp.series(runInstall, runRebuild); + +const cleanAll = () => Promise.resolve(); + +const runTestsWithFixture = (server, client) => () => new Promise((resolve, reject) => { + const fixture = `${server}_${client}`; + gulp.src(`${apiTestDir}/*.js`) + .pipe(mocha({ + reporter: 'mocha-jenkins-reporter', + require: [`${testDir}/fixtures/${fixture}.js`] + })) + .resume() // put the stream in flowing mode + .on('end', resolve) + .on('error', reject); +}); + +const testJsClientNativeServer = runTestsWithFixture('native', 'js'); +const testNativeClientJsServer = runTestsWithFixture('js', 'native'); +const testJsClientJsServer = runTestsWithFixture('js', 'js'); + +const test = semver.satisfies(process.version, NATIVE_SUPPORT_RANGE)? gulp.series( + testJsClientJsServer, + testJsClientNativeServer, + testNativeClientJsServer + ) : testJsClientJsServer; + +export { + install, + cleanAll, + test +}; diff --git a/test/interop/async_delay_queue.js b/test/interop/async_delay_queue.js index 43ac57387..f9a39b59a 100644 --- a/test/interop/async_delay_queue.js +++ b/test/interop/async_delay_queue.js @@ -39,9 +39,13 @@ AsyncDelayQueue.prototype.runNext = function() { var continueCallback = _.bind(this.runNext, this); if (next) { this.callback_pending = true; - setTimeout(function() { + if (next.delay === 0) { next.callback(continueCallback); - }, next.delay); + } else { + setTimeout(function() { + next.callback(continueCallback); + }, next.delay); + } } else { this.callback_pending = false; } diff --git a/test/interop/interop_client.js b/test/interop/interop_client.js index 5fad82bfc..57f4f1846 100644 --- a/test/interop/interop_client.js +++ b/test/interop/interop_client.js @@ -20,12 +20,18 @@ var fs = require('fs'); var path = require('path'); -// TODO(murgatroid99): use multiple grpc implementations -var grpc = require('grpc'); -var testProto = grpc.load({ - root: __dirname + '/../../packages/grpc-native-core/deps/grpc', - file: 'src/proto/grpc/testing/test.proto'}).grpc.testing; -var GoogleAuth = require('google-auth-library'); +var grpc = require('../any_grpc').client; +var protoLoader = require('../../packages/proto-loader'); + +const { GoogleAuth } = require('google-auth-library'); + +var protoPackage = protoLoader.loadSync( + 'src/proto/grpc/testing/test.proto', + {keepCase: true, + defaults: true, + enums: String, + includeDirs: [__dirname + '/../proto/']}); +var testProto = grpc.loadPackageDefinition(protoPackage).grpc.testing; var assert = require('assert'); @@ -44,10 +50,10 @@ var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin'; /** * Create a buffer filled with size zeroes * @param {number} size The length of the buffer - * @return {Buffer} The new buffer + * @return {Buffer} The New Buffer */ function zeroBuffer(size) { - var zeros = new Buffer(size); + var zeros = Buffer.alloc(size); zeros.fill(0); return zeros; } @@ -289,7 +295,7 @@ function customMetadata(client, done) { done = multiDone(done, 5); var metadata = new grpc.Metadata(); metadata.set(ECHO_INITIAL_KEY, 'test_initial_metadata_value'); - metadata.set(ECHO_TRAILING_KEY, new Buffer('ababab', 'hex')); + metadata.set(ECHO_TRAILING_KEY, Buffer.from('ababab', 'hex')); var arg = { response_type: 'COMPRESSABLE', response_size: 314159, @@ -348,7 +354,7 @@ function statusCodeAndMessage(client, done) { client.unaryCall(arg, function(err, resp) { assert(err); assert.strictEqual(err.code, 2); - assert.strictEqual(err.message, 'test status message'); + assert.strictEqual(err.details, 'test status message'); done(); }); var duplex = client.fullDuplexCall(); @@ -364,6 +370,22 @@ function statusCodeAndMessage(client, done) { duplex.end(); } +function specialStatusMessage(client, done) { + let expectedMessage = '\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n'; + let arg = { + response_status: { + code: 2, + message: expectedMessage + } + }; + client.unaryCall(arg, function(err, resp) { + assert(err); + assert.strictEqual(err.code, 2); + assert.strictEqual(err.details, expectedMessage); + done(); + }); +} + // NOTE: the client param to this function is from UnimplementedService function unimplementedService(client, done) { client.unimplementedCall({}, function(err, resp) { @@ -442,60 +464,35 @@ function oauth2Test(client, done, extra) { } function perRpcAuthTest(client, done, extra) { - (new GoogleAuth()).getApplicationDefault(function(err, credential) { + var arg = { + fill_username: true, + fill_oauth_scope: true + }; + const creds = grpc.credentials.createFromGoogleCredential(new GoogleAuth({scopes: extra.oauth_scope})); + client.unaryCall(arg, {credentials: creds}, function(err, resp) { assert.ifError(err); - var arg = { - fill_username: true, - fill_oauth_scope: true - }; - var scope = extra.oauth_scope; - if (credential.createScopedRequired() && scope) { - credential = credential.createScoped(scope); + assert.strictEqual(resp.username, SERVICE_ACCOUNT_EMAIL); + assert(extra.oauth_scope.indexOf(resp.oauth_scope) > -1); + if (done) { + done(); } - var creds = grpc.credentials.createFromGoogleCredential(credential); - client.unaryCall(arg, {credentials: creds}, function(err, resp) { - assert.ifError(err); - assert.strictEqual(resp.username, SERVICE_ACCOUNT_EMAIL); - assert(extra.oauth_scope.indexOf(resp.oauth_scope) > -1); - if (done) { - done(); - } - }); }); } function getApplicationCreds(scope, callback) { - (new GoogleAuth()).getApplicationDefault(function(err, credential) { - if (err) { - callback(err); - return; - } - if (credential.createScopedRequired() && scope) { - credential = credential.createScoped(scope); - } - callback(null, grpc.credentials.createFromGoogleCredential(credential)); - }); + callback(null, grpc.credentials.createFromGoogleCredential(new GoogleAuth({scopes: scope}))); } function getOauth2Creds(scope, callback) { - (new GoogleAuth()).getApplicationDefault(function(err, credential) { - if (err) { - callback(err); - return; - } - credential = credential.createScoped(scope); - credential.getAccessToken(function(err, token) { - if (err) { - callback(err); - return; - } - var updateMd = function(service_url, callback) { - var metadata = new grpc.Metadata(); - metadata.add('authorization', 'Bearer ' + token); - callback(null, metadata); - }; - callback(null, grpc.credentials.createFromMetadataGenerator(updateMd)); - }); + (new GoogleAuth({scopes: scope})).getAccessToken().then((token) => { + var updateMd = function(service_url, callback) { + var metadata = new grpc.Metadata(); + metadata.add('authorization', 'Bearer ' + token); + callback(null, metadata); + }; + callback(null, grpc.credentials.createFromMetadataGenerator(updateMd)); + }, (error) => { + callback(error); }); } @@ -525,6 +522,8 @@ var test_cases = { Client: testProto.TestService}, status_code_and_message: {run: statusCodeAndMessage, Client: testProto.TestService}, + special_status_message: {run: specialStatusMessage, + Client: testProto.TestService}, unimplemented_service: {run: unimplementedService, Client: testProto.UnimplementedService}, unimplemented_method: {run: unimplementedMethod, @@ -611,8 +610,12 @@ if (require.main === module) { }; runTest(argv.server_host + ':' + argv.server_port, argv.server_host_override, argv.test_case, argv.use_tls === 'true', argv.use_test_ca === 'true', - function () { - console.log('OK:', argv.test_case); + function (err) { + if (err) { + throw err; + } else { + console.log('OK:', argv.test_case); + } }, extra_args); } diff --git a/test/interop/interop_server.js b/test/interop/interop_server.js index 65bb088d3..b67ec5a8a 100644 --- a/test/interop/interop_server.js +++ b/test/interop/interop_server.js @@ -18,15 +18,21 @@ 'use strict'; +var assert = require('assert'); var fs = require('fs'); var path = require('path'); var _ = require('lodash'); var AsyncDelayQueue = require('./async_delay_queue'); -// TODO(murgatroid99): use multiple grpc implementations -var grpc = require('grpc'); -var testProto = grpc.load({ - root: __dirname + '/../../packages/grpc-native-core/deps/grpc', - file: 'src/proto/grpc/testing/test.proto'}).grpc.testing; +var grpc = require('../any_grpc').server; +// TODO(murgatroid99): do this import more cleanly +var protoLoader = require('../../packages/proto-loader'); +var protoPackage = protoLoader.loadSync( + 'src/proto/grpc/testing/test.proto', + {keepCase: true, + defaults: true, + enums: String, + includeDirs: [__dirname + '/../proto/']}); +var testProto = grpc.loadPackageDefinition(protoPackage).grpc.testing; var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial'; var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin'; @@ -34,10 +40,10 @@ var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin'; /** * Create a buffer filled with size zeroes * @param {number} size The length of the buffer - * @return {Buffer} The new buffer + * @return {Buffer} The New Buffer */ function zeroBuffer(size) { - var zeros = new Buffer(size); + var zeros = Buffer.alloc(size); zeros.fill(0); return zeros; } @@ -194,12 +200,16 @@ function handleHalfDuplex(call) { * Get a server object bound to the given port * @param {string} port Port to which to bind * @param {boolean} tls Indicates that the bound port should use TLS - * @return {{server: Server, port: number}} Server object bound to the support, - * and port number that the server is bound to + * @param {function(Error, {server: Server, port: number})} callback Callback + * to call with result or error + * @param {object?} options Optional additional options to use when + * constructing the server */ -function getServer(port, tls) { +function getServer(port, tls, callback, options) { // TODO(mlumish): enable TLS functionality - var options = {}; + if (!options) { + options = {}; + } var server_creds; if (tls) { var key_path = path.join(__dirname, '../data/server1.key'); @@ -222,8 +232,13 @@ function getServer(port, tls) { fullDuplexCall: handleFullDuplex, halfDuplexCall: handleHalfDuplex }); - var port_num = server.bind('0.0.0.0:' + port, server_creds); - return {server: server, port: port_num}; + server.bindAsync('0.0.0.0:' + port, server_creds, (err, port_num) => { + if (err) { + return callback(err); + } + + callback(null, {server: server, port: port_num}); + }); } if (require.main === module) { @@ -231,9 +246,11 @@ if (require.main === module) { var argv = parseArgs(process.argv, { string: ['port', 'use_tls'] }); - var server_obj = getServer(argv.port, argv.use_tls === 'true'); - console.log('Server attaching to port ' + argv.port); - server_obj.server.start(); + getServer(argv.port, argv.use_tls === 'true', (err, server_obj) => { + assert.ifError(err); + console.log('Server attaching to port ' + argv.port); + server_obj.server.start(); + }); } /** diff --git a/kokoro.bat b/test/kokoro.bat similarity index 88% rename from kokoro.bat rename to test/kokoro.bat index fc5fd8b22..a022a3f00 100644 --- a/kokoro.bat +++ b/test/kokoro.bat @@ -15,8 +15,6 @@ @echo "Starting Windows test" cd /d %~dp0 - -git submodule update --init -git submodule foreach --recursive git submodule update --init +cd .. .\run-tests.bat diff --git a/kokoro.sh b/test/kokoro.sh similarity index 79% rename from kokoro.sh rename to test/kokoro.sh index 3e51749c1..238a28a42 100755 --- a/kokoro.sh +++ b/test/kokoro.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Copyright 2017 gRPC authors. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,10 +14,6 @@ # limitations under the License. set -e -cd $(dirname $0) - -# Install gRPC and its submodules. -git submodule update --init -git submodule foreach --recursive git submodule update --init +cd $(dirname $0)/.. ./run-tests.sh diff --git a/test/kokoro/linux.cfg b/test/kokoro/linux.cfg index c6b2c88f6..63f88d399 100644 --- a/test/kokoro/linux.cfg +++ b/test/kokoro/linux.cfg @@ -11,14 +11,13 @@ # 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. - # Config file for Kokoro (in protobuf text format) # Location of the continuous shell script in repository. -build_file: "grpc-node/kokoro.sh" +build_file: "grpc-node/test/kokoro.sh" timeout_mins: 60 action { define_artifacts { regex: "github/grpc-node/reports/**/sponge_log.xml" } -} +} \ No newline at end of file diff --git a/test-grpc-submodule.sh b/test/kokoro/linux_aarch64.cfg old mode 100755 new mode 100644 similarity index 55% rename from test-grpc-submodule.sh rename to test/kokoro/linux_aarch64.cfg index 862b70c21..638748ab8 --- a/test-grpc-submodule.sh +++ b/test/kokoro/linux_aarch64.cfg @@ -1,4 +1,3 @@ -#!/bin/sh # Copyright 2017 gRPC authors. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,20 +11,14 @@ # 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. -# -# This script updates the gRPC submodule to a given reference and run tests - -# cd to gRPC-node root directory -cd $(dirname $0) - -cd packages/grpc-native-core/deps/grpc/ - -# PR references are needed to test PRs from grpc/grpc -git fetch --tags --progress https://github.com/grpc/grpc.git +refs/pull/*:refs/remotes/origin/pr/* -git checkout $@ -git submodule update --init -cd ../../../.. -packages/grpc-native-core/tools/buildgen/generate_projects.sh +# Config file for Kokoro (in protobuf text format) -./run-tests.sh +# Location of the continuous shell script in repository. +build_file: "grpc-node/test/kokoro_linux_aarch64.sh" +timeout_mins: 60 +action { + define_artifacts { + regex: "github/grpc-node/reports/**/sponge_log.xml" + } +} diff --git a/test/kokoro/macos.cfg b/test/kokoro/macos.cfg index c6b2c88f6..f40e6db43 100644 --- a/test/kokoro/macos.cfg +++ b/test/kokoro/macos.cfg @@ -15,7 +15,7 @@ # Config file for Kokoro (in protobuf text format) # Location of the continuous shell script in repository. -build_file: "grpc-node/kokoro.sh" +build_file: "grpc-node/test/kokoro.sh" timeout_mins: 60 action { define_artifacts { diff --git a/test/kokoro/windows.cfg b/test/kokoro/windows.cfg index e4a4524bd..c440966f7 100644 --- a/test/kokoro/windows.cfg +++ b/test/kokoro/windows.cfg @@ -15,5 +15,10 @@ # Config file for Kokoro (in protobuf text format) # Location of the continuous shell script in repository. -build_file: "grpc-node/kokoro.bat" +build_file: "grpc-node/test/kokoro.bat" timeout_mins: 60 +action { + define_artifacts { + regex: "github/grpc-node/reports/**/sponge_log.xml" + } +} diff --git a/test/kokoro/xds-v3-interop.cfg b/test/kokoro/xds-v3-interop.cfg new file mode 100644 index 000000000..75377fe16 --- /dev/null +++ b/test/kokoro/xds-v3-interop.cfg @@ -0,0 +1,24 @@ +# Copyright 2021 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/packages/grpc-js-xds/scripts/xds-v3.sh" +timeout_mins: 360 +action { + define_artifacts { + regex: "github/grpc/reports/**" + } +} diff --git a/test/kokoro/xds_k8s_lb.cfg b/test/kokoro/xds_k8s_lb.cfg new file mode 100644 index 000000000..3efb62f29 --- /dev/null +++ b/test/kokoro/xds_k8s_lb.cfg @@ -0,0 +1,30 @@ +# Copyright 2022 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/packages/grpc-js-xds/scripts/psm-interop-test-node.sh" +timeout_mins: 180 +action { + define_artifacts { + regex: "artifacts/**/*sponge_log.xml" + regex: "artifacts/**/*.log" + strip_prefix: "artifacts" + } +} +env_vars { + key: "PSM_TEST_SUITE" + value: "lb" +} diff --git a/test/kokoro/xds_k8s_url_map.cfg b/test/kokoro/xds_k8s_url_map.cfg new file mode 100644 index 000000000..bb6e6baf1 --- /dev/null +++ b/test/kokoro/xds_k8s_url_map.cfg @@ -0,0 +1,30 @@ +# Copyright 2022 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/packages/grpc-js-xds/scripts/psm-interop-test-node.sh" +timeout_mins: 180 +action { + define_artifacts { + regex: "artifacts/**/*sponge_log.xml" + regex: "artifacts/**/*.log" + strip_prefix: "artifacts" + } +} +env_vars { + key: "PSM_TEST_SUITE" + value: "url_map" +} diff --git a/test/kokoro_linux_aarch64.sh b/test/kokoro_linux_aarch64.sh new file mode 100755 index 000000000..508881e5f --- /dev/null +++ b/test/kokoro_linux_aarch64.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright 2021 The 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. + +set -ex +cd $(dirname $0)/.. + +test/aarch64/prepare_qemu.sh + +# better update submodules here. We could update the submodule when running +# under an emulator as well, but it comes with performance penalty. +git submodule update --init --recursive + +if [[ -t 0 ]]; then + DOCKER_TTY_ARGS="-it" +else + # The input device on kokoro is not a TTY, so -it does not work. + DOCKER_TTY_ARGS= +fi + +# the test command to run under an emulated aarch64 docker container. +# we only run tests for a single version of node, since tests under an emulator are significantly slower. +TEST_NODE_COMMAND="node_versions='12' ./run-tests.sh" + +# use an actual aarch64 docker image (with a real aarch64 node) to run build & test grpc-js under an emulator +# * mount the protobuf root as /work to be able to access the crosscompiled files +# * to avoid running the process inside docker as root (which can pollute the workspace with files owned by root), we force +# running under current user's UID and GID. To be able to do that, we need to provide a home directory for the user +# otherwise the UID would be homeless under the docker container (which can lead to various issues). For simplicity, +# we just run map the user's home to a throwaway temporary directory. +# TODO(jtattermusch): we're using arm64v8/node:12-stretch instead of arm64v8/node:12-buster because the buster-based image +# has a newer version of ssl that considers some of the ssl keys used for testing too short, making the tests +# fails with "error:140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small". +# See https://github.com/grpc/grpc-node/issues/1795 +docker run $DOCKER_TTY_ARGS --rm --user "$(id -u):$(id -g)" -e "HOME=/home/fake-user" -v "$(mktemp -d):/home/fake-user" -v "$(pwd)":/work -w /work arm64v8/node:12-stretch bash -c "$TEST_NODE_COMMAND" diff --git a/test/package.json b/test/package.json index e3aa0eebd..4f42f7bf7 100644 --- a/test/package.json +++ b/test/package.json @@ -14,13 +14,10 @@ } ], "dependencies": { - "async": "^2.0.1", - "body-parser": "^1.15.2", - "express": "^4.14.0", - "google-auth-library": "^0.9.2", - "google-protobuf": "^3.0.0", + "express": "^4.16.3", + "google-auth-library": "^6.1.0", + "grpc": "^1.24.2", "lodash": "^4.17.4", - "minimist": "^1.1.0", - "poisson-process": "^0.2.1" + "poisson-process": "^1.0.0" } } diff --git a/test/performance/benchmark_client.js b/test/performance/benchmark_client.js index 974edc01f..42605a2fd 100644 --- a/test/performance/benchmark_client.js +++ b/test/performance/benchmark_client.js @@ -36,18 +36,24 @@ var Histogram = require('./histogram'); var genericService = require('./generic_service'); // TODO(murgatroid99): use multiple grpc implementations -var grpc = require('grpc'); -var serviceProto = grpc.load({ - root: __dirname + '/../packages/grpc-native-core/ext/grpc', - file: 'src/proto/grpc/testing/services.proto'}).grpc.testing; +var grpc = require('../any_grpc').client; +var protoLoader = require('../../packages/proto-loader'); +var protoPackage = protoLoader.loadSync( + 'src/proto/grpc/testing/benchmark_service.proto', + {keepCase: true, + defaults: true, + enums: String, + oneofs: true, + includeDirs: [__dirname + '/../proto/']}); +var serviceProto = grpc.loadPackageDefinition(protoPackage).grpc.testing; /** * Create a buffer filled with size zeroes * @param {number} size The length of the buffer - * @return {Buffer} The new buffer + * @return {Buffer} The New Buffer */ function zeroBuffer(size) { - var zeros = new Buffer(size); + var zeros = Buffer.alloc(size); zeros.fill(0); return zeros; } @@ -82,7 +88,7 @@ function BenchmarkClient(server_targets, channels, histogram_params, if (security_params) { var ca_path; if (security_params.use_test_ca) { - ca_path = path.join(__dirname, '../test/data/ca.pem'); + ca_path = path.join(__dirname, '../data/ca.pem'); var ca_data = fs.readFileSync(ca_path); creds = grpc.credentials.createSsl(ca_data); } else { diff --git a/test/performance/benchmark_client_express.js b/test/performance/benchmark_client_express.js index 815843fed..f8be6d45e 100644 --- a/test/performance/benchmark_client_express.js +++ b/test/performance/benchmark_client_express.js @@ -60,7 +60,7 @@ function BenchmarkClient(server_targets, channels, histogram_params, protocol = https; this.request = _.bind(https.request, https); if (security_params.use_test_ca) { - ca_path = path.join(__dirname, '../test/data/ca.pem'); + ca_path = path.join(__dirname, '../data/ca.pem'); var ca_data = fs.readFileSync(ca_path); options.ca = ca_data; } diff --git a/test/performance/benchmark_server.js b/test/performance/benchmark_server.js index a39c5ec6e..64128b9d0 100644 --- a/test/performance/benchmark_server.js +++ b/test/performance/benchmark_server.js @@ -23,6 +23,7 @@ 'use strict'; +var assert = require('assert'); var fs = require('fs'); var path = require('path'); var EventEmitter = require('events'); @@ -30,19 +31,24 @@ var util = require('util'); var genericService = require('./generic_service'); -// TODO(murgatroid99): use multiple grpc implementations -var grpc = require('grpc'); -var serviceProto = grpc.load({ - root: __dirname + '/../packages/grpc-native-core/ext/grpc', - file: 'src/proto/grpc/testing/services.proto'}).grpc.testing; +var grpc = require('../any_grpc').server; +var protoLoader = require('../../packages/proto-loader'); +var protoPackage = protoLoader.loadSync( + 'src/proto/grpc/testing/benchmark_service.proto', + {keepCase: true, + defaults: true, + enums: String, + oneofs: true, + includeDirs: [__dirname + '/../proto']}); +var serviceProto = grpc.loadPackageDefinition(protoPackage).grpc.testing; /** * Create a buffer filled with size zeroes * @param {number} size The length of the buffer - * @return {Buffer} The new buffer + * @return {Buffer} The New Buffer */ function zeroBuffer(size) { - var zeros = new Buffer(size); + var zeros = Buffer.alloc(size); zeros.fill(0); return zeros; } @@ -106,8 +112,8 @@ function BenchmarkServer(host, port, tls, generic, response_size) { var server_creds; var host_override; if (tls) { - var key_path = path.join(__dirname, '../test/data/server1.key'); - var pem_path = path.join(__dirname, '../test/data/server1.pem'); + var key_path = path.join(__dirname, '../data/server1.key'); + var pem_path = path.join(__dirname, '../data/server1.pem'); var key_data = fs.readFileSync(key_path); var pem_data = fs.readFileSync(pem_path); @@ -124,7 +130,7 @@ function BenchmarkServer(host, port, tls, generic, response_size) { }; var server = new grpc.Server(options); - this.port = server.bind(host + ':' + port, server_creds); + if (generic) { server.addService(genericService, { unaryCall: makeUnaryGenericCall(response_size), @@ -137,6 +143,9 @@ function BenchmarkServer(host, port, tls, generic, response_size) { }); } this.server = server; + this.host = host; + this.port = port; + this.creds = server_creds; } util.inherits(BenchmarkServer, EventEmitter); @@ -145,10 +154,13 @@ util.inherits(BenchmarkServer, EventEmitter); * Start the benchmark server. */ BenchmarkServer.prototype.start = function() { - this.server.start(); - this.last_wall_time = process.hrtime(); - this.last_usage = process.cpuUsage(); - this.emit('started'); + this.server.bindAsync(this.host + ':' + this.port, this.creds, (err) => { + assert.ifError(err); + this.server.start(); + this.last_wall_time = process.hrtime(); + this.last_usage = process.cpuUsage(); + this.emit('started'); + }); }; /** diff --git a/test/performance/benchmark_server_express.js b/test/performance/benchmark_server_express.js index 73e54091a..657006f95 100644 --- a/test/performance/benchmark_server_express.js +++ b/test/performance/benchmark_server_express.js @@ -47,8 +47,8 @@ function BenchmarkServer(host, port, tls, generic, response_size) { this.input_port = port; if (tls) { var credentials = {}; - var key_path = path.join(__dirname, '../test/data/server1.key'); - var pem_path = path.join(__dirname, '../test/data/server1.pem'); + var key_path = path.join(__dirname, '../data/server1.key'); + var pem_path = path.join(__dirname, '../data/server1.pem'); var key_data = fs.readFileSync(key_path); var pem_data = fs.readFileSync(pem_path); diff --git a/test/performance/worker.js b/test/performance/worker.js index 5a3f114ec..86f17df2a 100644 --- a/test/performance/worker.js +++ b/test/performance/worker.js @@ -18,25 +18,36 @@ 'use strict'; +var assert = require('assert'); var console = require('console'); var WorkerServiceImpl = require('./worker_service_impl'); -// TODO(murgatroid99): use multiple grpc implementations -var grpc = require('grpc'); -var serviceProto = grpc.load({ - root: __dirname + '/../packages/grpc-native-core/ext/grpc', - file: 'src/proto/grpc/testing/services.proto'}).grpc.testing; +var grpc = require('../any_grpc').server; +var protoLoader = require('../../packages/proto-loader'); +var protoPackage = protoLoader.loadSync( + 'src/proto/grpc/testing/worker_service.proto', + {keepCase: true, + defaults: true, + enums: String, + oneofs: true, + includeDirs: [__dirname + '/../proto/']}); +var serviceProto = grpc.loadPackageDefinition(protoPackage).grpc.testing; -function runServer(port, benchmark_impl) { +function runServer(port, benchmark_impl, callback) { var server_creds = grpc.ServerCredentials.createInsecure(); var server = new grpc.Server(); server.addService(serviceProto.WorkerService.service, new WorkerServiceImpl(benchmark_impl, server)); var address = '0.0.0.0:' + port; - server.bind(address, server_creds); - server.start(); - console.log('running QPS worker on %s', address); - return server; + server.bindAsync(address, server_creds, (err) => { + if (err) { + return callback(err); + } + + server.start(); + console.log('running QPS worker on %s', address); + callback(null, server); + }); } if (require.main === module) { @@ -45,7 +56,9 @@ if (require.main === module) { var argv = parseArgs(process.argv, { string: ['driver_port', 'benchmark_impl'] }); - runServer(argv.driver_port, argv.benchmark_impl); + runServer(argv.driver_port, argv.benchmark_impl, (err, server) => { + assert.ifError(err); + }); } exports.runServer = runServer; diff --git a/test/proto/echo_service.proto b/test/proto/echo_service.proto new file mode 100644 index 000000000..414b421ec --- /dev/null +++ b/test/proto/echo_service.proto @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2018 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"; + +message EchoMessage { + string value = 1; + int32 value2 = 2; +} + +service EchoService { + rpc Echo (EchoMessage) returns (EchoMessage); + + rpc EchoClientStream (stream EchoMessage) returns (EchoMessage); + + rpc EchoServerStream (EchoMessage) returns (stream EchoMessage); + + rpc EchoBidiStream (stream EchoMessage) returns (stream EchoMessage); +} diff --git a/test/proto/src/proto/grpc/core/stats.proto b/test/proto/src/proto/grpc/core/stats.proto new file mode 100644 index 000000000..d853ba4df --- /dev/null +++ b/test/proto/src/proto/grpc/core/stats.proto @@ -0,0 +1,38 @@ +// Copyright 2017 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"; + +package grpc.core; + +message Bucket { + double start = 1; + uint64 count = 2; +} + +message Histogram { + repeated Bucket buckets = 1; +} + +message Metric { + string name = 1; + oneof value { + uint64 count = 10; + Histogram histogram = 11; + } +} + +message Stats { + repeated Metric metrics = 1; +} diff --git a/test/proto/src/proto/grpc/testing/benchmark_service.proto b/test/proto/src/proto/grpc/testing/benchmark_service.proto new file mode 100644 index 000000000..439183a73 --- /dev/null +++ b/test/proto/src/proto/grpc/testing/benchmark_service.proto @@ -0,0 +1,44 @@ +// 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. + +// An integration test service that covers all the method signature permutations +// of unary/streaming requests/responses. +syntax = "proto3"; + +import "src/proto/grpc/testing/messages.proto"; + +package grpc.testing; + +service BenchmarkService { + // One request followed by one response. + // The server returns the client payload as-is. + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // Repeated sequence of one request followed by one response. + // Should be called streaming ping-pong + // The server returns the client payload as-is on each response + rpc StreamingCall(stream SimpleRequest) returns (stream SimpleResponse); + + // Single-sided unbounded streaming from client to server + // The server returns the client payload as-is once the client does WritesDone + rpc StreamingFromClient(stream SimpleRequest) returns (SimpleResponse); + + // Single-sided unbounded streaming from server to client + // The server repeatedly returns the client payload as-is + rpc StreamingFromServer(SimpleRequest) returns (stream SimpleResponse); + + // Two-sided unbounded streaming between server to client + // Both sides send the content of their own choice to the other + rpc StreamingBothWays(stream SimpleRequest) returns (stream SimpleResponse); +} diff --git a/test/proto/src/proto/grpc/testing/control.proto b/test/proto/src/proto/grpc/testing/control.proto new file mode 100644 index 000000000..d22cd325d --- /dev/null +++ b/test/proto/src/proto/grpc/testing/control.proto @@ -0,0 +1,289 @@ +// 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"; + +import "src/proto/grpc/testing/payloads.proto"; +import "src/proto/grpc/testing/stats.proto"; + +package grpc.testing; + +enum ClientType { + // Many languages support a basic distinction between using + // sync or async client, and this allows the specification + SYNC_CLIENT = 0; + ASYNC_CLIENT = 1; + OTHER_CLIENT = 2; // used for some language-specific variants + CALLBACK_CLIENT = 3; +} + +enum ServerType { + SYNC_SERVER = 0; + ASYNC_SERVER = 1; + ASYNC_GENERIC_SERVER = 2; + OTHER_SERVER = 3; // used for some language-specific variants + CALLBACK_SERVER = 4; +} + +enum RpcType { + UNARY = 0; + STREAMING = 1; + STREAMING_FROM_CLIENT = 2; + STREAMING_FROM_SERVER = 3; + STREAMING_BOTH_WAYS = 4; +} + +// Parameters of poisson process distribution, which is a good representation +// of activity coming in from independent identical stationary sources. +message PoissonParams { + // The rate of arrivals (a.k.a. lambda parameter of the exp distribution). + double offered_load = 1; +} + +// Once an RPC finishes, immediately start a new one. +// No configuration parameters needed. +message ClosedLoopParams {} + +message LoadParams { + oneof load { + ClosedLoopParams closed_loop = 1; + PoissonParams poisson = 2; + }; +} + +// presence of SecurityParams implies use of TLS +message SecurityParams { + bool use_test_ca = 1; + string server_host_override = 2; + string cred_type = 3; +} + +message ChannelArg { + string name = 1; + oneof value { + string str_value = 2; + int32 int_value = 3; + } +} + +message ClientConfig { + // List of targets to connect to. At least one target needs to be specified. + repeated string server_targets = 1; + ClientType client_type = 2; + SecurityParams security_params = 3; + // How many concurrent RPCs to start for each channel. + // For synchronous client, use a separate thread for each outstanding RPC. + int32 outstanding_rpcs_per_channel = 4; + // Number of independent client channels to create. + // i-th channel will connect to server_target[i % server_targets.size()] + int32 client_channels = 5; + // Only for async client. Number of threads to use to start/manage RPCs. + int32 async_client_threads = 7; + RpcType rpc_type = 8; + // The requested load for the entire client (aggregated over all the threads). + LoadParams load_params = 10; + PayloadConfig payload_config = 11; + HistogramParams histogram_params = 12; + + // Specify the cores we should run the client on, if desired + repeated int32 core_list = 13; + int32 core_limit = 14; + + // If we use an OTHER_CLIENT client_type, this string gives more detail + string other_client_api = 15; + + repeated ChannelArg channel_args = 16; + + // Number of threads that share each completion queue + int32 threads_per_cq = 17; + + // Number of messages on a stream before it gets finished/restarted + int32 messages_per_stream = 18; + + // Use coalescing API when possible. + bool use_coalesce_api = 19; + + // If 0, disabled. Else, specifies the period between gathering latency + // medians in milliseconds. + int32 median_latency_collection_interval_millis = 20; + + // Number of client processes. 0 indicates no restriction. + int32 client_processes = 21; +} + +message ClientStatus { ClientStats stats = 1; } + +// Request current stats +message Mark { + // if true, the stats will be reset after taking their snapshot. + bool reset = 1; +} + +message ClientArgs { + oneof argtype { + ClientConfig setup = 1; + Mark mark = 2; + } +} + +message ServerConfig { + ServerType server_type = 1; + SecurityParams security_params = 2; + // Port on which to listen. Zero means pick unused port. + int32 port = 4; + // Only for async server. Number of threads used to serve the requests. + int32 async_server_threads = 7; + // Specify the number of cores to limit server to, if desired + int32 core_limit = 8; + // payload config, used in generic server. + // Note this must NOT be used in proto (non-generic) servers. For proto servers, + // 'response sizes' must be configured from the 'response_size' field of the + // 'SimpleRequest' objects in RPC requests. + PayloadConfig payload_config = 9; + + // Specify the cores we should run the server on, if desired + repeated int32 core_list = 10; + + // If we use an OTHER_SERVER client_type, this string gives more detail + string other_server_api = 11; + + // Number of threads that share each completion queue + int32 threads_per_cq = 12; + + // c++-only options (for now) -------------------------------- + + // Buffer pool size (no buffer pool specified if unset) + int32 resource_quota_size = 1001; + repeated ChannelArg channel_args = 1002; + + // Number of server processes. 0 indicates no restriction. + int32 server_processes = 21; +} + +message ServerArgs { + oneof argtype { + ServerConfig setup = 1; + Mark mark = 2; + } +} + +message ServerStatus { + ServerStats stats = 1; + // the port bound by the server + int32 port = 2; + // Number of cores available to the server + int32 cores = 3; +} + +message CoreRequest { +} + +message CoreResponse { + // Number of cores available on the server + int32 cores = 1; +} + +message Void { +} + +// A single performance scenario: input to qps_json_driver +message Scenario { + // Human readable name for this scenario + string name = 1; + // Client configuration + ClientConfig client_config = 2; + // Number of clients to start for the test + int32 num_clients = 3; + // Server configuration + ServerConfig server_config = 4; + // Number of servers to start for the test + int32 num_servers = 5; + // Warmup period, in seconds + int32 warmup_seconds = 6; + // Benchmark time, in seconds + int32 benchmark_seconds = 7; + // Number of workers to spawn locally (usually zero) + int32 spawn_local_worker_count = 8; +} + +// A set of scenarios to be run with qps_json_driver +message Scenarios { + repeated Scenario scenarios = 1; +} + +// Basic summary that can be computed from ClientStats and ServerStats +// once the scenario has finished. +message ScenarioResultSummary +{ + // Total number of operations per second over all clients. What is counted as 1 'operation' depends on the benchmark scenarios: + // For unary benchmarks, an operation is processing of a single unary RPC. + // For streaming benchmarks, an operation is processing of a single ping pong of request and response. + double qps = 1; + // QPS per server core. + double qps_per_server_core = 2; + // The total server cpu load based on system time across all server processes, expressed as percentage of a single cpu core. + // For example, 85 implies 85% of a cpu core, 125 implies 125% of a cpu core. Since we are accumulating the cpu load across all the server + // processes, the value could > 100 when there are multiple servers or a single server using multiple threads and cores. + // Same explanation for the total client cpu load below. + double server_system_time = 3; + // The total server cpu load based on user time across all server processes, expressed as percentage of a single cpu core. (85 => 85%, 125 => 125%) + double server_user_time = 4; + // The total client cpu load based on system time across all client processes, expressed as percentage of a single cpu core. (85 => 85%, 125 => 125%) + double client_system_time = 5; + // The total client cpu load based on user time across all client processes, expressed as percentage of a single cpu core. (85 => 85%, 125 => 125%) + double client_user_time = 6; + + // X% latency percentiles (in nanoseconds) + double latency_50 = 7; + double latency_90 = 8; + double latency_95 = 9; + double latency_99 = 10; + double latency_999 = 11; + + // server cpu usage percentage + double server_cpu_usage = 12; + + // Number of requests that succeeded/failed + double successful_requests_per_second = 13; + double failed_requests_per_second = 14; + + // Number of polls called inside completion queue per request + double client_polls_per_request = 15; + double server_polls_per_request = 16; + + // Queries per CPU-sec over all servers or clients + double server_queries_per_cpu_sec = 17; + double client_queries_per_cpu_sec = 18; +} + +// Results of a single benchmark scenario. +message ScenarioResult { + // Inputs used to run the scenario. + Scenario scenario = 1; + // Histograms from all clients merged into one histogram. + HistogramData latencies = 2; + // Client stats for each client + repeated ClientStats client_stats = 3; + // Server stats for each server + repeated ServerStats server_stats = 4; + // Number of cores available to each server + repeated int32 server_cores = 5; + // An after-the-fact computed summary + ScenarioResultSummary summary = 6; + // Information on success or failure of each worker + repeated bool client_success = 7; + repeated bool server_success = 8; + // Number of failed requests (one row per status code seen) + repeated RequestResultCount request_results = 9; +} diff --git a/test/proto/src/proto/grpc/testing/empty.proto b/test/proto/src/proto/grpc/testing/empty.proto new file mode 100644 index 000000000..cb3972062 --- /dev/null +++ b/test/proto/src/proto/grpc/testing/empty.proto @@ -0,0 +1,28 @@ + +// 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"; + +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/test/proto/src/proto/grpc/testing/messages.proto b/test/proto/src/proto/grpc/testing/messages.proto new file mode 100644 index 000000000..5b504f3b3 --- /dev/null +++ b/test/proto/src/proto/grpc/testing/messages.proto @@ -0,0 +1,209 @@ + +// Copyright 2015-2016 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. + +// 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; +} + +// 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 { + // 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; +} + +// The type of route that a client took to reach a server w.r.t. gRPCLB. +// The server must fill in "fallback" if it detects that the RPC reached +// the server via the "gRPCLB fallback" path, and "backend" if it detects +// that the RPC reached the server via "gRPCLB backend" path (i.e. if it got +// the address of this server from the gRPCLB server BalanceLoad RPC). Exactly +// how this detection is done is context and server dependent. +enum GrpclbRouteType { + // Server didn't detect the route that a client took to reach it. + GRPCLB_ROUTE_TYPE_UNKNOWN = 0; + // Indicates that a client reached a server via gRPCLB fallback. + GRPCLB_ROUTE_TYPE_FALLBACK = 1; + // Indicates that a client reached a server as a gRPCLB-given backend. + GRPCLB_ROUTE_TYPE_BACKEND = 2; +} + +// Unary request. +message SimpleRequest { + // 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; + + // Whether SimpleResponse should include server_id. + bool fill_server_id = 9; + + // Whether SimpleResponse should include grpclb_route_type. + bool fill_grpclb_route_type = 10; +} + +// 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; + + // Server ID. This must be unique among different server instances, + // but the same across all RPC's made to a particular server instance. + string server_id = 4; + // gRPCLB Path. + GrpclbRouteType grpclb_route_type = 5; + + // Server hostname. + string hostname = 6; +} + +// 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 { + // 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; +} + +message LoadBalancerStatsRequest { + // Request stats for the next num_rpcs sent by client. + int32 num_rpcs = 1; + // If num_rpcs have not completed within timeout_sec, return partial results. + int32 timeout_sec = 2; +} + +message LoadBalancerStatsResponse { + // The number of completed RPCs for each peer. + map rpcs_by_peer = 1; + // The number of RPCs that failed to record a remote peer. + int32 num_failures = 2; +} diff --git a/test/proto/src/proto/grpc/testing/metrics.proto b/test/proto/src/proto/grpc/testing/metrics.proto new file mode 100644 index 000000000..90dcba3ab --- /dev/null +++ b/test/proto/src/proto/grpc/testing/metrics.proto @@ -0,0 +1,49 @@ +// Copyright 2015-2016 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. + +// Contains the definitions for a metrics service and the type of metrics +// exposed by the service. +// +// Currently, 'Gauge' (i.e a metric that represents the measured value of +// something at an instant of time) is the only metric type supported by the +// service. +syntax = "proto3"; + +package grpc.testing; + +// Response message containing the gauge name and value +message GaugeResponse { + string name = 1; + oneof value { + int64 long_value = 2; + double double_value = 3; + string string_value = 4; + } +} + +// Request message containing the gauge name +message GaugeRequest { + string name = 1; +} + +message EmptyMessage {} + +service MetricsService { + // Returns the values of all the gauges that are currently being maintained by + // the service + rpc GetAllGauges(EmptyMessage) returns (stream GaugeResponse); + + // Returns the value of one gauge + rpc GetGauge(GaugeRequest) returns (GaugeResponse); +} diff --git a/test/proto/src/proto/grpc/testing/payloads.proto b/test/proto/src/proto/grpc/testing/payloads.proto new file mode 100644 index 000000000..76022656e --- /dev/null +++ b/test/proto/src/proto/grpc/testing/payloads.proto @@ -0,0 +1,40 @@ +// 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"; + +package grpc.testing; + +message ByteBufferParams { + int32 req_size = 1; + int32 resp_size = 2; +} + +message SimpleProtoParams { + int32 req_size = 1; + int32 resp_size = 2; +} + +message ComplexProtoParams { + // TODO (vpai): Fill this in once the details of complex, representative + // protos are decided +} + +message PayloadConfig { + oneof payload { + ByteBufferParams bytebuf_params = 1; + SimpleProtoParams simple_params = 2; + ComplexProtoParams complex_params = 3; + } +} diff --git a/test/proto/src/proto/grpc/testing/stats.proto b/test/proto/src/proto/grpc/testing/stats.proto new file mode 100644 index 000000000..705a60ab0 --- /dev/null +++ b/test/proto/src/proto/grpc/testing/stats.proto @@ -0,0 +1,83 @@ +// 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"; + +package grpc.testing; + +import "src/proto/grpc/core/stats.proto"; + +message ServerStats { + // wall clock time change in seconds since last reset + double time_elapsed = 1; + + // change in user time (in seconds) used by the server since last reset + double time_user = 2; + + // change in server time (in seconds) used by the server process and all + // threads since last reset + double time_system = 3; + + // change in total cpu time of the server (data from proc/stat) + uint64 total_cpu_time = 4; + + // change in idle time of the server (data from proc/stat) + uint64 idle_cpu_time = 5; + + // Number of polls called inside completion queue + uint64 cq_poll_count = 6; + + // Core library stats + grpc.core.Stats core_stats = 7; +} + +// Histogram params based on grpc/support/histogram.c +message HistogramParams { + double resolution = 1; // first bucket is [0, 1 + resolution) + double max_possible = 2; // use enough buckets to allow this value +} + +// Histogram data based on grpc/support/histogram.c +message HistogramData { + repeated uint32 bucket = 1; + double min_seen = 2; + double max_seen = 3; + double sum = 4; + double sum_of_squares = 5; + double count = 6; +} + +message RequestResultCount { + int32 status_code = 1; + int64 count = 2; +} + +message ClientStats { + // Latency histogram. Data points are in nanoseconds. + HistogramData latencies = 1; + + // See ServerStats for details. + double time_elapsed = 2; + double time_user = 3; + double time_system = 4; + + // Number of failed requests (one row per status code seen) + repeated RequestResultCount request_results = 5; + + // Number of polls called inside completion queue + uint64 cq_poll_count = 6; + + // Core library stats + grpc.core.Stats core_stats = 7; +} diff --git a/test/proto/src/proto/grpc/testing/test.proto b/test/proto/src/proto/grpc/testing/test.proto new file mode 100644 index 000000000..be5fd043b --- /dev/null +++ b/test/proto/src/proto/grpc/testing/test.proto @@ -0,0 +1,86 @@ + +// Copyright 2015-2016 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. + +// An integration test service that covers all the method signature permutations +// of unary/streaming requests/responses. + +syntax = "proto3"; + +import "src/proto/grpc/testing/empty.proto"; +import "src/proto/grpc/testing/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); +} + +// A service used to obtain stats for verifying LB behavior. +service LoadBalancerStatsService { + // Gets the backend distribution for RPCs sent by a test client. + rpc GetClientStats(LoadBalancerStatsRequest) + returns (LoadBalancerStatsResponse) {} +} diff --git a/test/proto/src/proto/grpc/testing/worker_service.proto b/test/proto/src/proto/grpc/testing/worker_service.proto new file mode 100644 index 000000000..aee817d48 --- /dev/null +++ b/test/proto/src/proto/grpc/testing/worker_service.proto @@ -0,0 +1,45 @@ +// 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. + +// An integration test service that covers all the method signature permutations +// of unary/streaming requests/responses. +syntax = "proto3"; + +import "src/proto/grpc/testing/control.proto"; + +package grpc.testing; + +service WorkerService { + // Start server with specified workload. + // First request sent specifies the ServerConfig followed by ServerStatus + // response. After that, a "Mark" can be sent anytime to request the latest + // stats. Closing the stream will initiate shutdown of the test server + // and once the shutdown has finished, the OK status is sent to terminate + // this RPC. + rpc RunServer(stream ServerArgs) returns (stream ServerStatus); + + // Start client with specified workload. + // First request sent specifies the ClientConfig followed by ClientStatus + // response. After that, a "Mark" can be sent anytime to request the latest + // stats. Closing the stream will initiate shutdown of the test client + // and once the shutdown has finished, the OK status is sent to terminate + // this RPC. + rpc RunClient(stream ClientArgs) returns (stream ClientStatus); + + // Just return the core count - unary call + rpc CoreCount(CoreRequest) returns (CoreResponse); + + // Quit this worker + rpc QuitWorker(Void) returns (Void); +} diff --git a/packages/grpc-native-core/test/test_messages.proto b/test/proto/test_messages.proto similarity index 100% rename from packages/grpc-native-core/test/test_messages.proto rename to test/proto/test_messages.proto diff --git a/test/api/test_service.proto b/test/proto/test_service.proto similarity index 97% rename from test/api/test_service.proto rename to test/proto/test_service.proto index b16dfecca..a0e49842b 100644 --- a/test/api/test_service.proto +++ b/test/proto/test_service.proto @@ -16,6 +16,7 @@ syntax = "proto3"; message Request { bool error = 1; + string message = 2; } message Response { diff --git a/test/stress/metrics_client.js b/test/stress/metrics_client.js index c0cb3eb8c..a5c94059a 100644 --- a/test/stress/metrics_client.js +++ b/test/stress/metrics_client.js @@ -21,7 +21,7 @@ // TODO(murgatroid99): use multiple grpc implementations var grpc = require('grpc'); -var proto = grpc.load(__dirname + '/../packages/grpc-native-core/ext/grpc/src/proto/grpc/testing/metrics.proto'); +var proto = grpc.load(__dirname + '/../proto/src/proto/grpc/testing/metrics.proto'); var metrics = proto.grpc.testing; function main() { diff --git a/test/stress/metrics_server.js b/test/stress/metrics_server.js index 70848f3bc..d8e39086f 100644 --- a/test/stress/metrics_server.js +++ b/test/stress/metrics_server.js @@ -23,7 +23,7 @@ var _ = require('lodash'); // TODO(murgatroid99): use multiple grpc implementations var grpc = require('grpc'); -var proto = grpc.load(__dirname + '/../packages/grpc-native-core/ext/grpc/src/proto/grpc/testing/metrics.proto'); +var proto = grpc.load(__dirname + '/../proto/src/proto/grpc/testing/metrics.proto'); var metrics = proto.grpc.testing; function getGauge(call, callback) { diff --git a/tools/release/native/Dockerfile b/tools/release/native/Dockerfile new file mode 100644 index 000000000..7346d9aff --- /dev/null +++ b/tools/release/native/Dockerfile @@ -0,0 +1,9 @@ +FROM debian:stretch + +RUN apt-get update +RUN apt-get install -y cmake curl build-essential python libc6-dev-i386 lib32stdc++-6-dev jq + +RUN mkdir /usr/local/nvm +ENV NVM_DIR /usr/local/nvm + +RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..dbc5c8989 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "lib": [ + "es2015" + ] + } +} diff --git a/util.js b/util.js new file mode 100644 index 000000000..cd0464bd9 --- /dev/null +++ b/util.js @@ -0,0 +1,40 @@ +/* + * Copyright 2019 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. + * + */ + +const path = require('path'); +const del = require('del'); +const fs = require('fs'); +const makeDir = require('make-dir'); + +// synchronously link a module +const linkSync = (base, from, to) => { + from = path.resolve(base, from); + to = path.resolve(base, to); + try { + fs.lstatSync(from); + console.log('link: deleting', from); + del.sync(from); + } catch (e) { + makeDir.sync(path.dirname(from)); + } + console.log('link: linking', from, '->', to); + fs.symlinkSync(to, from, 'junction'); +}; + +module.exports = { + linkSync +}; \ No newline at end of file