mirror of https://github.com/grpc/grpc-java.git
Compare commits
93 Commits
Author | SHA1 | Date |
---|---|---|
|
c7202c0db5 | |
|
028afbe352 | |
|
afdbecb235 | |
|
2039266ebc | |
|
43bef65cf9 | |
|
437e03dc98 | |
|
6462ef9a11 | |
|
95d16d85c8 | |
|
f50726d32e | |
|
06707f7c38 | |
|
efcdebb904 | |
|
f30964ab82 | |
|
7040417eee | |
|
a40c8cf5a4 | |
|
8b46ad58c3 | |
|
d947c80f99 | |
|
6ffcbd927e | |
|
36fe276a50 | |
|
ba0a7329da | |
|
28f14255ce | |
|
7e982e48a1 | |
|
c3ef1ab034 | |
|
8f09b96899 | |
|
42e1829b37 | |
|
c4256add4d | |
|
6ff8ecac09 | |
|
80217275db | |
|
2e96fbf1e8 | |
|
a37d3eb349 | |
|
1fc4ab0bb2 | |
|
6935d3a115 | |
|
d7d70c6905 | |
|
d352540a02 | |
|
5a8326f1c7 | |
|
a8de9f07ab | |
|
9d191b31b5 | |
|
01bd63d88f | |
|
94532a6b56 | |
|
6dfa03c51c | |
|
919370172d | |
|
ca99a8c478 | |
|
2ee4f9b488 | |
|
74aee11389 | |
|
64322c3243 | |
|
af7efeb9f5 | |
|
ebc6d3e932 | |
|
d374b26b68 | |
|
f99b2aaef8 | |
|
30d40a6179 | |
|
9a6bdc70af | |
|
e6e7bcadaf | |
|
922dc8a999 | |
|
d2d8ed8efa | |
|
f07eb47cac | |
|
2604ce8a55 | |
|
1c43098990 | |
|
d5b4fb51c2 | |
|
8974a306af | |
|
d88ef97a87 | |
|
240f731e00 | |
|
26bd0eee47 | |
|
6f69363d90 | |
|
6cc2ff1ced | |
|
13fe008044 | |
|
30f6a4db77 | |
|
297ab05efe | |
|
a16d655919 | |
|
6afacf589e | |
|
4ee662fbcf | |
|
1fd29bc804 | |
|
dc192f5c5e | |
|
c206428749 | |
|
4cd7881086 | |
|
4c73999102 | |
|
482dc5c1c3 | |
|
efe9ccc22c | |
|
48d08e643e | |
|
6bad600592 | |
|
379fbaa380 | |
|
8044a56ad2 | |
|
142e378cea | |
|
22cf7cf2ac | |
|
83538cdae3 | |
|
d124007ff4 | |
|
46485c8b62 | |
|
9406d3b2a0 | |
|
9d439d4a44 | |
|
f8700a13ad | |
|
2fb09578a8 | |
|
480640dc2f | |
|
b88536a17d | |
|
b089761486 | |
|
59adeb9d47 |
|
@ -102,6 +102,9 @@ jobs:
|
||||||
- name: Run bazel build
|
- name: Run bazel build
|
||||||
run: bazelisk build //... --enable_bzlmod=${{ matrix.bzlmod }}
|
run: bazelisk build //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||||
|
|
||||||
|
- name: Run bazel test
|
||||||
|
run: bazelisk test //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||||
|
|
||||||
- name: Run example bazel build
|
- name: Run example bazel build
|
||||||
run: bazelisk build //... --enable_bzlmod=${{ matrix.bzlmod }}
|
run: bazelisk build //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||||
working-directory: ./examples
|
working-directory: ./examples
|
||||||
|
|
|
@ -33,7 +33,6 @@ java_library(
|
||||||
"//api",
|
"//api",
|
||||||
"//protobuf",
|
"//protobuf",
|
||||||
"//stub",
|
"//stub",
|
||||||
"//stub:javax_annotation",
|
|
||||||
"@com_google_protobuf//:protobuf_java",
|
"@com_google_protobuf//:protobuf_java",
|
||||||
artifact("com.google.code.findbugs:jsr305"),
|
artifact("com.google.code.findbugs:jsr305"),
|
||||||
artifact("com.google.guava:guava"),
|
artifact("com.google.guava:guava"),
|
||||||
|
@ -47,7 +46,6 @@ java_library(
|
||||||
"//api",
|
"//api",
|
||||||
"//protobuf-lite",
|
"//protobuf-lite",
|
||||||
"//stub",
|
"//stub",
|
||||||
"//stub:javax_annotation",
|
|
||||||
artifact("com.google.code.findbugs:jsr305"),
|
artifact("com.google.code.findbugs:jsr305"),
|
||||||
artifact("com.google.guava:guava"),
|
artifact("com.google.guava:guava"),
|
||||||
],
|
],
|
||||||
|
@ -67,6 +65,5 @@ java_library(
|
||||||
visibility = ["//:__subpackages__"],
|
visibility = ["//:__subpackages__"],
|
||||||
exports = [
|
exports = [
|
||||||
artifact("com.google.auto.value:auto-value-annotations"),
|
artifact("com.google.auto.value:auto-value-annotations"),
|
||||||
artifact("org.apache.tomcat:annotations-api"), # @Generated for Java 9+
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -44,11 +44,11 @@ This section is only necessary if you are making changes to the code
|
||||||
generation. Most users only need to use `skipCodegen=true` as discussed above.
|
generation. Most users only need to use `skipCodegen=true` as discussed above.
|
||||||
|
|
||||||
### Build Protobuf
|
### Build Protobuf
|
||||||
The codegen plugin is C++ code and requires protobuf 21.7 or later.
|
The codegen plugin is C++ code and requires protobuf 22.5 or later.
|
||||||
|
|
||||||
For Linux, Mac and MinGW:
|
For Linux, Mac and MinGW:
|
||||||
```
|
```
|
||||||
$ PROTOBUF_VERSION=21.7
|
$ PROTOBUF_VERSION=22.5
|
||||||
$ curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-all-$PROTOBUF_VERSION.tar.gz
|
$ curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-all-$PROTOBUF_VERSION.tar.gz
|
||||||
$ tar xzf protobuf-all-$PROTOBUF_VERSION.tar.gz
|
$ tar xzf protobuf-all-$PROTOBUF_VERSION.tar.gz
|
||||||
$ cd protobuf-$PROTOBUF_VERSION
|
$ cd protobuf-$PROTOBUF_VERSION
|
||||||
|
|
|
@ -11,8 +11,6 @@ for general contribution guidelines.
|
||||||
- [ejona86](https://github.com/ejona86), Google LLC
|
- [ejona86](https://github.com/ejona86), Google LLC
|
||||||
- [jdcormie](https://github.com/jdcormie), Google LLC
|
- [jdcormie](https://github.com/jdcormie), Google LLC
|
||||||
- [kannanjgithub](https://github.com/kannanjgithub), Google LLC
|
- [kannanjgithub](https://github.com/kannanjgithub), Google LLC
|
||||||
- [larry-safran](https://github.com/larry-safran), Google LLC
|
|
||||||
- [markb74](https://github.com/markb74), Google LLC
|
|
||||||
- [ran-su](https://github.com/ran-su), Google LLC
|
- [ran-su](https://github.com/ran-su), Google LLC
|
||||||
- [sergiitk](https://github.com/sergiitk), Google LLC
|
- [sergiitk](https://github.com/sergiitk), Google LLC
|
||||||
- [temawi](https://github.com/temawi), Google LLC
|
- [temawi](https://github.com/temawi), Google LLC
|
||||||
|
@ -26,7 +24,9 @@ for general contribution guidelines.
|
||||||
- [ericgribkoff](https://github.com/ericgribkoff)
|
- [ericgribkoff](https://github.com/ericgribkoff)
|
||||||
- [jiangtaoli2016](https://github.com/jiangtaoli2016)
|
- [jiangtaoli2016](https://github.com/jiangtaoli2016)
|
||||||
- [jtattermusch](https://github.com/jtattermusch)
|
- [jtattermusch](https://github.com/jtattermusch)
|
||||||
|
- [larry-safran](https://github.com/larry-safran)
|
||||||
- [louiscryan](https://github.com/louiscryan)
|
- [louiscryan](https://github.com/louiscryan)
|
||||||
|
- [markb74](https://github.com/markb74)
|
||||||
- [nicolasnoble](https://github.com/nicolasnoble)
|
- [nicolasnoble](https://github.com/nicolasnoble)
|
||||||
- [nmittler](https://github.com/nmittler)
|
- [nmittler](https://github.com/nmittler)
|
||||||
- [sanjaypujare](https://github.com/sanjaypujare)
|
- [sanjaypujare](https://github.com/sanjaypujare)
|
||||||
|
|
60
MODULE.bazel
60
MODULE.bazel
|
@ -2,13 +2,13 @@ module(
|
||||||
name = "grpc-java",
|
name = "grpc-java",
|
||||||
compatibility_level = 0,
|
compatibility_level = 0,
|
||||||
repo_name = "io_grpc_grpc_java",
|
repo_name = "io_grpc_grpc_java",
|
||||||
version = "1.73.0-SNAPSHOT", # CURRENT_GRPC_VERSION
|
version = "1.76.0-SNAPSHOT", # CURRENT_GRPC_VERSION
|
||||||
)
|
)
|
||||||
|
|
||||||
# GRPC_DEPS_START
|
# GRPC_DEPS_START
|
||||||
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||||
"com.google.android:annotations:4.1.1.4",
|
"com.google.android:annotations:4.1.1.4",
|
||||||
"com.google.api.grpc:proto-google-common-protos:2.51.0",
|
"com.google.api.grpc:proto-google-common-protos:2.59.2",
|
||||||
"com.google.auth:google-auth-library-credentials:1.24.1",
|
"com.google.auth:google-auth-library-credentials:1.24.1",
|
||||||
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
||||||
"com.google.auto.value:auto-value-annotations:1.11.0",
|
"com.google.auto.value:auto-value-annotations:1.11.0",
|
||||||
|
@ -19,64 +19,44 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||||
"com.google.guava:failureaccess:1.0.1",
|
"com.google.guava:failureaccess:1.0.1",
|
||||||
"com.google.guava:guava:33.3.1-android",
|
"com.google.guava:guava:33.3.1-android",
|
||||||
"com.google.re2j:re2j:1.8",
|
"com.google.re2j:re2j:1.8",
|
||||||
"com.google.s2a.proto.v2:s2a-proto:0.1.1",
|
"com.google.s2a.proto.v2:s2a-proto:0.1.2",
|
||||||
"com.google.truth:truth:1.4.2",
|
"com.google.truth:truth:1.4.2",
|
||||||
"com.squareup.okhttp:okhttp:2.7.5",
|
"com.squareup.okhttp:okhttp:2.7.5",
|
||||||
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
||||||
"io.netty:netty-buffer:4.1.110.Final",
|
"io.netty:netty-buffer:4.1.124.Final",
|
||||||
"io.netty:netty-codec-http2:4.1.110.Final",
|
"io.netty:netty-codec-http2:4.1.124.Final",
|
||||||
"io.netty:netty-codec-http:4.1.110.Final",
|
"io.netty:netty-codec-http:4.1.124.Final",
|
||||||
"io.netty:netty-codec-socks:4.1.110.Final",
|
"io.netty:netty-codec-socks:4.1.124.Final",
|
||||||
"io.netty:netty-codec:4.1.110.Final",
|
"io.netty:netty-codec:4.1.124.Final",
|
||||||
"io.netty:netty-common:4.1.110.Final",
|
"io.netty:netty-common:4.1.124.Final",
|
||||||
"io.netty:netty-handler-proxy:4.1.110.Final",
|
"io.netty:netty-handler-proxy:4.1.124.Final",
|
||||||
"io.netty:netty-handler:4.1.110.Final",
|
"io.netty:netty-handler:4.1.124.Final",
|
||||||
"io.netty:netty-resolver:4.1.110.Final",
|
"io.netty:netty-resolver:4.1.124.Final",
|
||||||
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
||||||
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
||||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final",
|
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.124.Final",
|
||||||
"io.netty:netty-transport-native-unix-common:4.1.110.Final",
|
"io.netty:netty-transport-native-unix-common:4.1.124.Final",
|
||||||
"io.netty:netty-transport:4.1.110.Final",
|
"io.netty:netty-transport:4.1.124.Final",
|
||||||
"io.opencensus:opencensus-api:0.31.0",
|
"io.opencensus:opencensus-api:0.31.0",
|
||||||
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
||||||
"io.perfmark:perfmark-api:0.27.0",
|
"io.perfmark:perfmark-api:0.27.0",
|
||||||
"junit:junit:4.13.2",
|
"junit:junit:4.13.2",
|
||||||
"org.apache.tomcat:annotations-api:6.0.53",
|
|
||||||
"org.checkerframework:checker-qual:3.12.0",
|
"org.checkerframework:checker-qual:3.12.0",
|
||||||
"org.codehaus.mojo:animal-sniffer-annotations:1.24",
|
"org.codehaus.mojo:animal-sniffer-annotations:1.24",
|
||||||
]
|
]
|
||||||
# GRPC_DEPS_END
|
# GRPC_DEPS_END
|
||||||
|
|
||||||
|
bazel_dep(name = "bazel_jar_jar", version = "0.1.7")
|
||||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||||
bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5")
|
bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5")
|
||||||
|
|
||||||
# CEL Spec may be removed when cncf/xds MODULE is no longer using protobuf 27.x
|
|
||||||
bazel_dep(name = "cel-spec", repo_name = "dev_cel", version = "0.15.0")
|
|
||||||
bazel_dep(name = "grpc", repo_name = "com_github_grpc_grpc", version = "1.56.3.bcr.1")
|
|
||||||
bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58")
|
bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58")
|
||||||
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
|
# Protobuf 25.5+ is incompatible with Bazel 7 with bzlmod
|
||||||
|
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "24.4")
|
||||||
bazel_dep(name = "rules_cc", version = "0.0.9")
|
bazel_dep(name = "rules_cc", version = "0.0.9")
|
||||||
bazel_dep(name = "rules_java", version = "5.3.5")
|
bazel_dep(name = "rules_java", version = "5.3.5")
|
||||||
bazel_dep(name = "rules_go", repo_name = "io_bazel_rules_go", version = "0.46.0")
|
|
||||||
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
||||||
bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
|
bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
|
||||||
|
|
||||||
non_module_deps = use_extension("//:repositories.bzl", "grpc_java_repositories_extension")
|
|
||||||
|
|
||||||
use_repo(
|
|
||||||
non_module_deps,
|
|
||||||
"com_github_cncf_xds",
|
|
||||||
"envoy_api",
|
|
||||||
)
|
|
||||||
|
|
||||||
grpc_repo_deps_ext = use_extension("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_repo_deps_ext")
|
|
||||||
|
|
||||||
use_repo(
|
|
||||||
grpc_repo_deps_ext,
|
|
||||||
"com_envoyproxy_protoc_gen_validate",
|
|
||||||
"opencensus_proto",
|
|
||||||
)
|
|
||||||
|
|
||||||
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||||
|
|
||||||
maven.install(
|
maven.install(
|
||||||
|
@ -203,7 +183,3 @@ maven.override(
|
||||||
coordinates = "io.grpc:grpc-util",
|
coordinates = "io.grpc:grpc-util",
|
||||||
target = "@io_grpc_grpc_java//util",
|
target = "@io_grpc_grpc_java//util",
|
||||||
)
|
)
|
||||||
|
|
||||||
switched_rules = use_extension("@com_google_googleapis//:extensions.bzl", "switched_rules")
|
|
||||||
|
|
||||||
switched_rules.use_languages(java = True)
|
|
||||||
|
|
32
README.md
32
README.md
|
@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start
|
||||||
guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC
|
guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC
|
||||||
basics](https://grpc.io/docs/languages/java/basics).
|
basics](https://grpc.io/docs/languages/java/basics).
|
||||||
|
|
||||||
The [examples](https://github.com/grpc/grpc-java/tree/v1.72.0/examples) and the
|
The [examples](https://github.com/grpc/grpc-java/tree/v1.75.0/examples) and the
|
||||||
[Android example](https://github.com/grpc/grpc-java/tree/v1.72.0/examples/android)
|
[Android example](https://github.com/grpc/grpc-java/tree/v1.75.0/examples/android)
|
||||||
are standalone projects that showcase the usage of gRPC.
|
are standalone projects that showcase the usage of gRPC.
|
||||||
|
|
||||||
Download
|
Download
|
||||||
|
@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.grpc</groupId>
|
<groupId>io.grpc</groupId>
|
||||||
<artifactId>grpc-netty-shaded</artifactId>
|
<artifactId>grpc-netty-shaded</artifactId>
|
||||||
<version>1.72.0</version>
|
<version>1.75.0</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.grpc</groupId>
|
<groupId>io.grpc</groupId>
|
||||||
<artifactId>grpc-protobuf</artifactId>
|
<artifactId>grpc-protobuf</artifactId>
|
||||||
<version>1.72.0</version>
|
<version>1.75.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.grpc</groupId>
|
<groupId>io.grpc</groupId>
|
||||||
<artifactId>grpc-stub</artifactId>
|
<artifactId>grpc-stub</artifactId>
|
||||||
<version>1.72.0</version>
|
<version>1.75.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency> <!-- necessary for Java 9+ -->
|
<dependency> <!-- necessary for Java 9+ -->
|
||||||
<groupId>org.apache.tomcat</groupId>
|
<groupId>org.apache.tomcat</groupId>
|
||||||
|
@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
||||||
|
|
||||||
Or for Gradle with non-Android, add to your dependencies:
|
Or for Gradle with non-Android, add to your dependencies:
|
||||||
```gradle
|
```gradle
|
||||||
runtimeOnly 'io.grpc:grpc-netty-shaded:1.72.0'
|
runtimeOnly 'io.grpc:grpc-netty-shaded:1.75.0'
|
||||||
implementation 'io.grpc:grpc-protobuf:1.72.0'
|
implementation 'io.grpc:grpc-protobuf:1.75.0'
|
||||||
implementation 'io.grpc:grpc-stub:1.72.0'
|
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||||
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
||||||
```
|
```
|
||||||
|
|
||||||
For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and
|
For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and
|
||||||
`grpc-protobuf-lite` instead of `grpc-protobuf`:
|
`grpc-protobuf-lite` instead of `grpc-protobuf`:
|
||||||
```gradle
|
```gradle
|
||||||
implementation 'io.grpc:grpc-okhttp:1.72.0'
|
implementation 'io.grpc:grpc-okhttp:1.75.0'
|
||||||
implementation 'io.grpc:grpc-protobuf-lite:1.72.0'
|
implementation 'io.grpc:grpc-protobuf-lite:1.75.0'
|
||||||
implementation 'io.grpc:grpc-stub:1.72.0'
|
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||||
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -99,10 +99,10 @@ For [Bazel](https://bazel.build), you can either
|
||||||
(with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below).
|
(with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below).
|
||||||
|
|
||||||
[the JARs]:
|
[the JARs]:
|
||||||
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.72.0
|
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.75.0
|
||||||
|
|
||||||
Development snapshots are available in [Sonatypes's snapshot
|
Development snapshots are available in [Sonatypes's snapshot
|
||||||
repository](https://oss.sonatype.org/content/repositories/snapshots/).
|
repository](https://central.sonatype.com/repository/maven-snapshots/).
|
||||||
|
|
||||||
Generated Code
|
Generated Code
|
||||||
--------------
|
--------------
|
||||||
|
@ -131,7 +131,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use
|
||||||
<configuration>
|
<configuration>
|
||||||
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
|
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
|
||||||
<pluginId>grpc-java</pluginId>
|
<pluginId>grpc-java</pluginId>
|
||||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.72.0:exe:${os.detected.classifier}</pluginArtifact>
|
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.75.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
@ -161,7 +161,7 @@ protobuf {
|
||||||
}
|
}
|
||||||
plugins {
|
plugins {
|
||||||
grpc {
|
grpc {
|
||||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.72.0'
|
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
|
@ -194,7 +194,7 @@ protobuf {
|
||||||
}
|
}
|
||||||
plugins {
|
plugins {
|
||||||
grpc {
|
grpc {
|
||||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.72.0'
|
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
|
|
|
@ -160,7 +160,7 @@ Tagging the Release
|
||||||
repository can then be `released`, which will begin the process of pushing
|
repository can then be `released`, which will begin the process of pushing
|
||||||
the new artifacts to Maven Central (the staging repository will be destroyed
|
the new artifacts to Maven Central (the staging repository will be destroyed
|
||||||
in the process). You can see the complete process for releasing to Maven
|
in the process). You can see the complete process for releasing to Maven
|
||||||
Central on the [OSSRH site](https://central.sonatype.org/pages/releasing-the-deployment.html).
|
Central on the [OSSRH site](https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#deploying).
|
||||||
|
|
||||||
10. We have containers for each release to detect compatibility regressions with
|
10. We have containers for each release to detect compatibility regressions with
|
||||||
old releases. Generate one for the new release by following the [GCR image
|
old releases. Generate one for the new release by following the [GCR image
|
||||||
|
|
|
@ -400,7 +400,8 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver
|
||||||
1.59.x | 4.1.97.Final | 2.0.61.Final
|
1.59.x | 4.1.97.Final | 2.0.61.Final
|
||||||
1.60.x-1.66.x | 4.1.100.Final | 2.0.61.Final
|
1.60.x-1.66.x | 4.1.100.Final | 2.0.61.Final
|
||||||
1.67.x-1.70.x | 4.1.110.Final | 2.0.65.Final
|
1.67.x-1.70.x | 4.1.110.Final | 2.0.65.Final
|
||||||
1.71.x- | 4.1.110.Final | 2.0.70.Final
|
1.71.x-1.74.x | 4.1.110.Final | 2.0.70.Final
|
||||||
|
1.75.x- | 4.1.124.Final | 2.0.72.Final
|
||||||
|
|
||||||
_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
|
_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
|
||||||
|
|
||||||
|
|
|
@ -22,20 +22,19 @@ load("//:repositories.bzl", "grpc_java_repositories")
|
||||||
|
|
||||||
grpc_java_repositories()
|
grpc_java_repositories()
|
||||||
|
|
||||||
|
load("@bazel_jar_jar//:jar_jar.bzl", "jar_jar_repositories")
|
||||||
|
|
||||||
|
jar_jar_repositories()
|
||||||
|
|
||||||
load("@com_google_protobuf//:protobuf_deps.bzl", "PROTOBUF_MAVEN_ARTIFACTS")
|
load("@com_google_protobuf//:protobuf_deps.bzl", "PROTOBUF_MAVEN_ARTIFACTS")
|
||||||
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
|
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
|
||||||
|
|
||||||
protobuf_deps()
|
protobuf_deps()
|
||||||
|
|
||||||
load("@envoy_api//bazel:repositories.bzl", "api_dependencies")
|
|
||||||
|
|
||||||
api_dependencies()
|
|
||||||
|
|
||||||
load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
|
load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
|
||||||
|
|
||||||
switched_rules_by_language(
|
switched_rules_by_language(
|
||||||
name = "com_google_googleapis_imports",
|
name = "com_google_googleapis_imports",
|
||||||
java = True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
maven_install(
|
maven_install(
|
||||||
|
|
|
@ -4,9 +4,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/gcp/handshaker.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class HandshakerServiceGrpc {
|
public final class HandshakerServiceGrpc {
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import io.grpc.Channel;
|
||||||
import io.grpc.ClientCall;
|
import io.grpc.ClientCall;
|
||||||
import io.grpc.ManagedChannel;
|
import io.grpc.ManagedChannel;
|
||||||
import io.grpc.MethodDescriptor;
|
import io.grpc.MethodDescriptor;
|
||||||
|
import io.grpc.internal.GrpcUtil;
|
||||||
import io.grpc.internal.SharedResourceHolder.Resource;
|
import io.grpc.internal.SharedResourceHolder.Resource;
|
||||||
import io.grpc.netty.NettyChannelBuilder;
|
import io.grpc.netty.NettyChannelBuilder;
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
@ -45,6 +46,9 @@ final class HandshakerServiceChannel {
|
||||||
return new ChannelResource(handshakerAddress);
|
return new ChannelResource(handshakerAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final boolean EXPERIMENTAL_ALTS_HANDSHAKER_KEEPALIVE_PARAMS =
|
||||||
|
GrpcUtil.getFlag("GRPC_EXPERIMENTAL_ALTS_HANDSHAKER_KEEPALIVE_PARAMS", false);
|
||||||
|
|
||||||
private static class ChannelResource implements Resource<Channel> {
|
private static class ChannelResource implements Resource<Channel> {
|
||||||
private final String target;
|
private final String target;
|
||||||
|
|
||||||
|
@ -57,12 +61,16 @@ final class HandshakerServiceChannel {
|
||||||
/* Use its own event loop thread pool to avoid blocking. */
|
/* Use its own event loop thread pool to avoid blocking. */
|
||||||
EventLoopGroup eventGroup =
|
EventLoopGroup eventGroup =
|
||||||
new NioEventLoopGroup(1, new DefaultThreadFactory("handshaker pool", true));
|
new NioEventLoopGroup(1, new DefaultThreadFactory("handshaker pool", true));
|
||||||
ManagedChannel channel = NettyChannelBuilder.forTarget(target)
|
NettyChannelBuilder channelBuilder =
|
||||||
|
NettyChannelBuilder.forTarget(target)
|
||||||
.channelType(NioSocketChannel.class, InetSocketAddress.class)
|
.channelType(NioSocketChannel.class, InetSocketAddress.class)
|
||||||
.directExecutor()
|
.directExecutor()
|
||||||
.eventLoopGroup(eventGroup)
|
.eventLoopGroup(eventGroup)
|
||||||
.usePlaintext()
|
.usePlaintext();
|
||||||
.build();
|
if (EXPERIMENTAL_ALTS_HANDSHAKER_KEEPALIVE_PARAMS) {
|
||||||
|
channelBuilder.keepAliveTime(10, TimeUnit.MINUTES).keepAliveTimeout(10, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
ManagedChannel channel = channelBuilder.build();
|
||||||
return new EventLoopHoldingChannel(channel, eventGroup);
|
return new EventLoopHoldingChannel(channel, eventGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'io.grpc.android.integrationtest'
|
namespace = 'io.grpc.android.integrationtest'
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
|
@ -41,7 +41,7 @@ android {
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
multiDexEnabled true
|
multiDexEnabled = true
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug { minifyEnabled false }
|
debug { minifyEnabled false }
|
||||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* A service used to obtain stats for verifying LB behavior.
|
* A service used to obtain stats for verifying LB behavior.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class LoadBalancerStatsServiceGrpc {
|
public final class LoadBalancerStatsServiceGrpc {
|
||||||
|
|
||||||
|
@ -245,8 +242,8 @@ public final class LoadBalancerStatsServiceGrpc {
|
||||||
* Gets the backend distribution for RPCs sent by a test client.
|
* Gets the backend distribution for RPCs sent by a test client.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) {
|
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getGetClientStatsMethod(), getCallOptions(), request);
|
getChannel(), getGetClientStatsMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,8 +252,8 @@ public final class LoadBalancerStatsServiceGrpc {
|
||||||
* Gets the accumulated stats for RPCs sent by a test client.
|
* Gets the accumulated stats for RPCs sent by a test client.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) {
|
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request);
|
getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/metrics.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class MetricsServiceGrpc {
|
public final class MetricsServiceGrpc {
|
||||||
|
|
||||||
|
@ -245,8 +242,8 @@ public final class MetricsServiceGrpc {
|
||||||
* Returns the value of one gauge
|
* Returns the value of one gauge
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) {
|
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getGetGaugeMethod(), getCallOptions(), request);
|
getChannel(), getGetGaugeMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* A service used to control reconnect server.
|
* A service used to control reconnect server.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class ReconnectServiceGrpc {
|
public final class ReconnectServiceGrpc {
|
||||||
|
|
||||||
|
@ -230,15 +227,15 @@ public final class ReconnectServiceGrpc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.Messages.ReconnectParams request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.Messages.ReconnectParams request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getStartMethod(), getCallOptions(), request);
|
getChannel(), getStartMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getStopMethod(), getCallOptions(), request);
|
getChannel(), getStopMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* performance with various types of payload.
|
* performance with various types of payload.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class TestServiceGrpc {
|
public final class TestServiceGrpc {
|
||||||
|
|
||||||
|
@ -576,8 +573,8 @@ public final class TestServiceGrpc {
|
||||||
* One empty request followed by one empty response.
|
* One empty request followed by one empty response.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getEmptyCallMethod(), getCallOptions(), request);
|
getChannel(), getEmptyCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,8 +583,8 @@ public final class TestServiceGrpc {
|
||||||
* One request followed by one response.
|
* One request followed by one response.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
|
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,8 +595,8 @@ public final class TestServiceGrpc {
|
||||||
* satisfy subsequent requests.
|
* satisfy subsequent requests.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
|
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
|
getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,8 +661,8 @@ public final class TestServiceGrpc {
|
||||||
* to test the behavior when clients call unimplemented methods.
|
* to test the behavior when clients call unimplemented methods.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* that case.
|
* that case.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class UnimplementedServiceGrpc {
|
public final class UnimplementedServiceGrpc {
|
||||||
|
|
||||||
|
@ -199,8 +196,8 @@ public final class UnimplementedServiceGrpc {
|
||||||
* A call that no server should implement
|
* A call that no server should implement
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* A service to dynamically update the configuration of an xDS test client.
|
* A service to dynamically update the configuration of an xDS test client.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class XdsUpdateClientConfigureServiceGrpc {
|
public final class XdsUpdateClientConfigureServiceGrpc {
|
||||||
|
|
||||||
|
@ -194,8 +191,8 @@ public final class XdsUpdateClientConfigureServiceGrpc {
|
||||||
* Update the tes client's configuration.
|
* Update the tes client's configuration.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) {
|
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getConfigureMethod(), getCallOptions(), request);
|
getChannel(), getConfigureMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* A service to remotely control health status of an xDS test server.
|
* A service to remotely control health status of an xDS test server.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class XdsUpdateHealthServiceGrpc {
|
public final class XdsUpdateHealthServiceGrpc {
|
||||||
|
|
||||||
|
@ -230,15 +227,15 @@ public final class XdsUpdateHealthServiceGrpc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty setServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty setServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getSetServingMethod(), getCallOptions(), request);
|
getChannel(), getSetServingMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getSetNotServingMethod(), getCallOptions(), request);
|
getChannel(), getSetNotServingMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* A service used to obtain stats for verifying LB behavior.
|
* A service used to obtain stats for verifying LB behavior.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class LoadBalancerStatsServiceGrpc {
|
public final class LoadBalancerStatsServiceGrpc {
|
||||||
|
|
||||||
|
@ -245,8 +242,8 @@ public final class LoadBalancerStatsServiceGrpc {
|
||||||
* Gets the backend distribution for RPCs sent by a test client.
|
* Gets the backend distribution for RPCs sent by a test client.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) {
|
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getGetClientStatsMethod(), getCallOptions(), request);
|
getChannel(), getGetClientStatsMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,8 +252,8 @@ public final class LoadBalancerStatsServiceGrpc {
|
||||||
* Gets the accumulated stats for RPCs sent by a test client.
|
* Gets the accumulated stats for RPCs sent by a test client.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) {
|
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request);
|
getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/metrics.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class MetricsServiceGrpc {
|
public final class MetricsServiceGrpc {
|
||||||
|
|
||||||
|
@ -245,8 +242,8 @@ public final class MetricsServiceGrpc {
|
||||||
* Returns the value of one gauge
|
* Returns the value of one gauge
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) {
|
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getGetGaugeMethod(), getCallOptions(), request);
|
getChannel(), getGetGaugeMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* A service used to control reconnect server.
|
* A service used to control reconnect server.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class ReconnectServiceGrpc {
|
public final class ReconnectServiceGrpc {
|
||||||
|
|
||||||
|
@ -230,15 +227,15 @@ public final class ReconnectServiceGrpc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.Messages.ReconnectParams request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.Messages.ReconnectParams request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getStartMethod(), getCallOptions(), request);
|
getChannel(), getStartMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getStopMethod(), getCallOptions(), request);
|
getChannel(), getStopMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* performance with various types of payload.
|
* performance with various types of payload.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class TestServiceGrpc {
|
public final class TestServiceGrpc {
|
||||||
|
|
||||||
|
@ -576,8 +573,8 @@ public final class TestServiceGrpc {
|
||||||
* One empty request followed by one empty response.
|
* One empty request followed by one empty response.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getEmptyCallMethod(), getCallOptions(), request);
|
getChannel(), getEmptyCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,8 +583,8 @@ public final class TestServiceGrpc {
|
||||||
* One request followed by one response.
|
* One request followed by one response.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
|
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,8 +595,8 @@ public final class TestServiceGrpc {
|
||||||
* satisfy subsequent requests.
|
* satisfy subsequent requests.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
|
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
|
getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,8 +661,8 @@ public final class TestServiceGrpc {
|
||||||
* to test the behavior when clients call unimplemented methods.
|
* to test the behavior when clients call unimplemented methods.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* that case.
|
* that case.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class UnimplementedServiceGrpc {
|
public final class UnimplementedServiceGrpc {
|
||||||
|
|
||||||
|
@ -199,8 +196,8 @@ public final class UnimplementedServiceGrpc {
|
||||||
* A call that no server should implement
|
* A call that no server should implement
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* A service to dynamically update the configuration of an xDS test client.
|
* A service to dynamically update the configuration of an xDS test client.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class XdsUpdateClientConfigureServiceGrpc {
|
public final class XdsUpdateClientConfigureServiceGrpc {
|
||||||
|
|
||||||
|
@ -194,8 +191,8 @@ public final class XdsUpdateClientConfigureServiceGrpc {
|
||||||
* Update the tes client's configuration.
|
* Update the tes client's configuration.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) {
|
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getConfigureMethod(), getCallOptions(), request);
|
getChannel(), getConfigureMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* A service to remotely control health status of an xDS test server.
|
* A service to remotely control health status of an xDS test server.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/test.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class XdsUpdateHealthServiceGrpc {
|
public final class XdsUpdateHealthServiceGrpc {
|
||||||
|
|
||||||
|
@ -230,15 +227,15 @@ public final class XdsUpdateHealthServiceGrpc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty setServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty setServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getSetServingMethod(), getCallOptions(), request);
|
getChannel(), getSetServingMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getSetNotServingMethod(), getCallOptions(), request);
|
getChannel(), getSetNotServingMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,20 +7,20 @@ plugins {
|
||||||
description = 'gRPC: Android'
|
description = 'gRPC: Android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'io.grpc.android'
|
namespace = 'io.grpc.android'
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
compileSdkVersion 34
|
compileSdkVersion 34
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 22
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
lintOptions { abortOnError true }
|
lintOptions { abortOnError = true }
|
||||||
publishing {
|
publishing {
|
||||||
singleVariant('release') {
|
singleVariant('release') {
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
|
|
|
@ -217,7 +217,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
||||||
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
||||||
unregisterRunnable =
|
unregisterRunnable =
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
||||||
|
@ -231,7 +230,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
||||||
context.registerReceiver(networkReceiver, networkIntentFilter);
|
context.registerReceiver(networkReceiver, networkIntentFilter);
|
||||||
unregisterRunnable =
|
unregisterRunnable =
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
context.unregisterReceiver(networkReceiver);
|
context.unregisterReceiver(networkReceiver);
|
||||||
|
|
|
@ -1189,6 +1189,10 @@ public abstract class LoadBalancer {
|
||||||
* Returns a {@link SynchronizationContext} that runs tasks in the same Synchronization Context
|
* Returns a {@link SynchronizationContext} that runs tasks in the same Synchronization Context
|
||||||
* as that the callback methods on the {@link LoadBalancer} interface are run in.
|
* as that the callback methods on the {@link LoadBalancer} interface are run in.
|
||||||
*
|
*
|
||||||
|
* <p>Work added to the synchronization context might not run immediately, so LB implementations
|
||||||
|
* must be careful to ensure that any assumptions still hold when it is executed. In particular,
|
||||||
|
* the LB might have been shut down or subchannels might have changed state.
|
||||||
|
*
|
||||||
* <p>Pro-tip: in order to call {@link SynchronizationContext#schedule}, you need to provide a
|
* <p>Pro-tip: in order to call {@link SynchronizationContext#schedule}, you need to provide a
|
||||||
* {@link ScheduledExecutorService}. {@link #getScheduledExecutorService} is provided for your
|
* {@link ScheduledExecutorService}. {@link #getScheduledExecutorService} is provided for your
|
||||||
* convenience.
|
* convenience.
|
||||||
|
|
|
@ -22,6 +22,8 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
@ -32,8 +34,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -325,7 +325,7 @@ public final class Metadata {
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
Set<String> ks = new HashSet<>(size);
|
Set<String> ks = Sets.newHashSetWithExpectedSize(size);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
ks.add(new String(name(i), 0 /* hibyte */));
|
ks.add(new String(name(i), 0 /* hibyte */));
|
||||||
}
|
}
|
||||||
|
@ -526,7 +526,7 @@ public final class Metadata {
|
||||||
public void merge(Metadata other, Set<Key<?>> keys) {
|
public void merge(Metadata other, Set<Key<?>> keys) {
|
||||||
Preconditions.checkNotNull(other, "other");
|
Preconditions.checkNotNull(other, "other");
|
||||||
// Use ByteBuffer for equals and hashCode.
|
// Use ByteBuffer for equals and hashCode.
|
||||||
Map<ByteBuffer, Key<?>> asciiKeys = new HashMap<>(keys.size());
|
Map<ByteBuffer, Key<?>> asciiKeys = Maps.newHashMapWithExpectedSize(keys.size());
|
||||||
for (Key<?> key : keys) {
|
for (Key<?> key : keys) {
|
||||||
asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
|
asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,9 @@ public abstract class NameResolver {
|
||||||
* {@link ResolutionResult#getAddressesOrError()} is empty, {@link #onError(Status)} will be
|
* {@link ResolutionResult#getAddressesOrError()} is empty, {@link #onError(Status)} will be
|
||||||
* called.
|
* called.
|
||||||
*
|
*
|
||||||
|
* <p>Newer NameResolver implementations should prefer calling onResult2. This method exists to
|
||||||
|
* facilitate older {@link Listener} implementations to migrate to {@link Listener2}.
|
||||||
|
*
|
||||||
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
|
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
|
||||||
* @since 1.21.0
|
* @since 1.21.0
|
||||||
*/
|
*/
|
||||||
|
@ -248,6 +251,10 @@ public abstract class NameResolver {
|
||||||
* Handles a name resolving error from the resolver. The listener is responsible for eventually
|
* Handles a name resolving error from the resolver. The listener is responsible for eventually
|
||||||
* invoking {@link NameResolver#refresh()} to re-attempt resolution.
|
* invoking {@link NameResolver#refresh()} to re-attempt resolution.
|
||||||
*
|
*
|
||||||
|
* <p>New NameResolver implementations should prefer calling onResult2 which will have the
|
||||||
|
* address resolution error in {@link ResolutionResult}'s addressesOrError. This method exists
|
||||||
|
* to facilitate older implementations using {@link Listener} to migrate to {@link Listener2}.
|
||||||
|
*
|
||||||
* @param error a non-OK status
|
* @param error a non-OK status
|
||||||
* @since 1.21.0
|
* @since 1.21.0
|
||||||
*/
|
*/
|
||||||
|
@ -255,9 +262,14 @@ public abstract class NameResolver {
|
||||||
public abstract void onError(Status error);
|
public abstract void onError(Status error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles updates on resolved addresses and attributes.
|
* Handles updates on resolved addresses and attributes. Must be called from the same
|
||||||
|
* {@link SynchronizationContext} available in {@link NameResolver.Args} that is passed
|
||||||
|
* from the channel.
|
||||||
*
|
*
|
||||||
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
|
* @param resolutionResult the resolved server addresses or error in address resolution,
|
||||||
|
* attributes, and Service Config or error
|
||||||
|
* @return status indicating whether the resolutionResult was accepted by the listener,
|
||||||
|
* typically the result from a load balancer.
|
||||||
* @since 1.66
|
* @since 1.66
|
||||||
*/
|
*/
|
||||||
public Status onResult2(ResolutionResult resolutionResult) {
|
public Status onResult2(ResolutionResult resolutionResult) {
|
||||||
|
@ -303,6 +315,7 @@ public abstract class NameResolver {
|
||||||
@Nullable private final Executor executor;
|
@Nullable private final Executor executor;
|
||||||
@Nullable private final String overrideAuthority;
|
@Nullable private final String overrideAuthority;
|
||||||
@Nullable private final MetricRecorder metricRecorder;
|
@Nullable private final MetricRecorder metricRecorder;
|
||||||
|
@Nullable private final NameResolverRegistry nameResolverRegistry;
|
||||||
@Nullable private final IdentityHashMap<Key<?>, Object> customArgs;
|
@Nullable private final IdentityHashMap<Key<?>, Object> customArgs;
|
||||||
|
|
||||||
private Args(Builder builder) {
|
private Args(Builder builder) {
|
||||||
|
@ -316,6 +329,7 @@ public abstract class NameResolver {
|
||||||
this.executor = builder.executor;
|
this.executor = builder.executor;
|
||||||
this.overrideAuthority = builder.overrideAuthority;
|
this.overrideAuthority = builder.overrideAuthority;
|
||||||
this.metricRecorder = builder.metricRecorder;
|
this.metricRecorder = builder.metricRecorder;
|
||||||
|
this.nameResolverRegistry = builder.nameResolverRegistry;
|
||||||
this.customArgs = cloneCustomArgs(builder.customArgs);
|
this.customArgs = cloneCustomArgs(builder.customArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,6 +461,18 @@ public abstract class NameResolver {
|
||||||
return metricRecorder;
|
return metricRecorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link NameResolverRegistry} that the Channel uses to look for {@link
|
||||||
|
* NameResolver}s.
|
||||||
|
*
|
||||||
|
* @since 1.74.0
|
||||||
|
*/
|
||||||
|
public NameResolverRegistry getNameResolverRegistry() {
|
||||||
|
if (nameResolverRegistry == null) {
|
||||||
|
throw new IllegalStateException("NameResolverRegistry is not set in Builder");
|
||||||
|
}
|
||||||
|
return nameResolverRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -461,6 +487,7 @@ public abstract class NameResolver {
|
||||||
.add("executor", executor)
|
.add("executor", executor)
|
||||||
.add("overrideAuthority", overrideAuthority)
|
.add("overrideAuthority", overrideAuthority)
|
||||||
.add("metricRecorder", metricRecorder)
|
.add("metricRecorder", metricRecorder)
|
||||||
|
.add("nameResolverRegistry", nameResolverRegistry)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,6 +507,7 @@ public abstract class NameResolver {
|
||||||
builder.setOffloadExecutor(executor);
|
builder.setOffloadExecutor(executor);
|
||||||
builder.setOverrideAuthority(overrideAuthority);
|
builder.setOverrideAuthority(overrideAuthority);
|
||||||
builder.setMetricRecorder(metricRecorder);
|
builder.setMetricRecorder(metricRecorder);
|
||||||
|
builder.setNameResolverRegistry(nameResolverRegistry);
|
||||||
builder.customArgs = cloneCustomArgs(customArgs);
|
builder.customArgs = cloneCustomArgs(customArgs);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
@ -508,6 +536,7 @@ public abstract class NameResolver {
|
||||||
private Executor executor;
|
private Executor executor;
|
||||||
private String overrideAuthority;
|
private String overrideAuthority;
|
||||||
private MetricRecorder metricRecorder;
|
private MetricRecorder metricRecorder;
|
||||||
|
private NameResolverRegistry nameResolverRegistry;
|
||||||
private IdentityHashMap<Key<?>, Object> customArgs;
|
private IdentityHashMap<Key<?>, Object> customArgs;
|
||||||
|
|
||||||
Builder() {
|
Builder() {
|
||||||
|
@ -614,6 +643,16 @@ public abstract class NameResolver {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link Args#getNameResolverRegistry}. This is an optional field.
|
||||||
|
*
|
||||||
|
* @since 1.74.0
|
||||||
|
*/
|
||||||
|
public Builder setNameResolverRegistry(NameResolverRegistry registry) {
|
||||||
|
this.nameResolverRegistry = registry;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an {@link Args}.
|
* Builds an {@link Args}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -166,6 +166,11 @@ public final class NameResolverRegistry {
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
|
logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
list.add(Class.forName("io.grpc.binder.internal.IntentNameResolverProvider"));
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
logger.log(Level.FINE, "Unable to find IntentNameResolverProvider", e);
|
||||||
|
}
|
||||||
return Collections.unmodifiableList(list);
|
return Collections.unmodifiableList(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/services.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class BenchmarkServiceGrpc {
|
public final class BenchmarkServiceGrpc {
|
||||||
|
|
||||||
|
@ -401,8 +398,8 @@ public final class BenchmarkServiceGrpc {
|
||||||
* The server returns the client payload as-is.
|
* The server returns the client payload as-is.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.benchmarks.proto.Messages.SimpleResponse unaryCall(io.grpc.benchmarks.proto.Messages.SimpleRequest request) {
|
public io.grpc.benchmarks.proto.Messages.SimpleResponse unaryCall(io.grpc.benchmarks.proto.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/services.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class ReportQpsScenarioServiceGrpc {
|
public final class ReportQpsScenarioServiceGrpc {
|
||||||
|
|
||||||
|
@ -180,8 +177,8 @@ public final class ReportQpsScenarioServiceGrpc {
|
||||||
* Report results of a QPS test benchmark scenario.
|
* Report results of a QPS test benchmark scenario.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.benchmarks.proto.Control.Void reportScenario(io.grpc.benchmarks.proto.Control.ScenarioResult request) {
|
public io.grpc.benchmarks.proto.Control.Void reportScenario(io.grpc.benchmarks.proto.Control.ScenarioResult request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getReportScenarioMethod(), getCallOptions(), request);
|
getChannel(), getReportScenarioMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
|
||||||
value = "by gRPC proto compiler",
|
|
||||||
comments = "Source: grpc/testing/services.proto")
|
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class WorkerServiceGrpc {
|
public final class WorkerServiceGrpc {
|
||||||
|
|
||||||
|
@ -390,8 +387,8 @@ public final class WorkerServiceGrpc {
|
||||||
* Just return the core count - unary call
|
* Just return the core count - unary call
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.benchmarks.proto.Control.CoreResponse coreCount(io.grpc.benchmarks.proto.Control.CoreRequest request) {
|
public io.grpc.benchmarks.proto.Control.CoreResponse coreCount(io.grpc.benchmarks.proto.Control.CoreRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getCoreCountMethod(), getCallOptions(), request);
|
getChannel(), getCoreCountMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,8 +397,8 @@ public final class WorkerServiceGrpc {
|
||||||
* Quit this worker
|
* Quit this worker
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.benchmarks.proto.Control.Void quitWorker(io.grpc.benchmarks.proto.Control.Void request) {
|
public io.grpc.benchmarks.proto.Control.Void quitWorker(io.grpc.benchmarks.proto.Control.Void request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getQuitWorkerMethod(), getCallOptions(), request);
|
getChannel(), getQuitWorkerMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,28 +6,28 @@ plugins {
|
||||||
description = 'gRPC BinderChannel'
|
description = 'gRPC BinderChannel'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'io.grpc.binder'
|
namespace = 'io.grpc.binder'
|
||||||
compileSdkVersion 34
|
compileSdkVersion 34
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility 1.8
|
sourceCompatibility 1.8
|
||||||
targetCompatibility 1.8
|
targetCompatibility 1.8
|
||||||
}
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 22
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
multiDexEnabled true
|
multiDexEnabled = true
|
||||||
}
|
}
|
||||||
lintOptions { abortOnError false }
|
lintOptions { abortOnError = false }
|
||||||
publishing {
|
publishing {
|
||||||
singleVariant('release') {
|
singleVariant('release') {
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
withJavadocJar()
|
withJavadocJar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
testFixtures { enable true }
|
testFixtures { enable = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -72,6 +72,7 @@ dependencies {
|
||||||
androidTestImplementation testFixtures(project(':grpc-core'))
|
androidTestImplementation testFixtures(project(':grpc-core'))
|
||||||
|
|
||||||
testFixturesImplementation libraries.guava.testlib
|
testFixturesImplementation libraries.guava.testlib
|
||||||
|
testFixturesImplementation testFixtures(project(':grpc-core'))
|
||||||
}
|
}
|
||||||
|
|
||||||
import net.ltgt.gradle.errorprone.CheckSeverity
|
import net.ltgt.gradle.errorprone.CheckSeverity
|
||||||
|
|
|
@ -11,11 +11,13 @@
|
||||||
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
|
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="action1"/>
|
<action android:name="action1"/>
|
||||||
|
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service android:name="io.grpc.binder.HostServices$HostService2" android:exported="false">
|
<service android:name="io.grpc.binder.HostServices$HostService2" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="action2"/>
|
<action android:name="action2"/>
|
||||||
|
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
|
@ -23,6 +23,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
@ -39,7 +40,6 @@ import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
||||||
import io.grpc.ManagedChannel;
|
import io.grpc.ManagedChannel;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
import io.grpc.MethodDescriptor;
|
import io.grpc.MethodDescriptor;
|
||||||
import io.grpc.NameResolverRegistry;
|
|
||||||
import io.grpc.ServerCall;
|
import io.grpc.ServerCall;
|
||||||
import io.grpc.ServerCall.Listener;
|
import io.grpc.ServerCall.Listener;
|
||||||
import io.grpc.ServerCallHandler;
|
import io.grpc.ServerCallHandler;
|
||||||
|
@ -49,7 +49,6 @@ import io.grpc.ServerServiceDefinition;
|
||||||
import io.grpc.Status.Code;
|
import io.grpc.Status.Code;
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
import io.grpc.internal.GrpcUtil;
|
import io.grpc.internal.GrpcUtil;
|
||||||
import io.grpc.internal.testing.FakeNameResolverProvider;
|
|
||||||
import io.grpc.stub.ClientCalls;
|
import io.grpc.stub.ClientCalls;
|
||||||
import io.grpc.stub.MetadataUtils;
|
import io.grpc.stub.MetadataUtils;
|
||||||
import io.grpc.stub.ServerCalls;
|
import io.grpc.stub.ServerCalls;
|
||||||
|
@ -77,7 +76,6 @@ public final class BinderChannelSmokeTest {
|
||||||
|
|
||||||
private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100;
|
private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100;
|
||||||
private static final String MSG = "Some text which will be repeated many many times";
|
private static final String MSG = "Some text which will be repeated many many times";
|
||||||
private static final String SERVER_TARGET_URI = "fake://server";
|
|
||||||
private static final Metadata.Key<PoisonParcelable> POISON_KEY =
|
private static final Metadata.Key<PoisonParcelable> POISON_KEY =
|
||||||
ParcelableUtils.metadataKey("poison-bin", PoisonParcelable.CREATOR);
|
ParcelableUtils.metadataKey("poison-bin", PoisonParcelable.CREATOR);
|
||||||
|
|
||||||
|
@ -99,7 +97,6 @@ public final class BinderChannelSmokeTest {
|
||||||
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
|
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
FakeNameResolverProvider fakeNameResolverProvider;
|
|
||||||
ManagedChannel channel;
|
ManagedChannel channel;
|
||||||
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
|
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
|
||||||
AtomicReference<PeerUid> clientUidCapture = new AtomicReference<>();
|
AtomicReference<PeerUid> clientUidCapture = new AtomicReference<>();
|
||||||
|
@ -138,8 +135,6 @@ public final class BinderChannelSmokeTest {
|
||||||
PeerUids.newPeerIdentifyingServerInterceptor());
|
PeerUids.newPeerIdentifyingServerInterceptor());
|
||||||
|
|
||||||
AndroidComponentAddress serverAddress = HostServices.allocateService(appContext);
|
AndroidComponentAddress serverAddress = HostServices.allocateService(appContext);
|
||||||
fakeNameResolverProvider = new FakeNameResolverProvider(SERVER_TARGET_URI, serverAddress);
|
|
||||||
NameResolverRegistry.getDefaultRegistry().register(fakeNameResolverProvider);
|
|
||||||
HostServices.configureService(
|
HostServices.configureService(
|
||||||
serverAddress,
|
serverAddress,
|
||||||
HostServices.serviceParamsBuilder()
|
HostServices.serviceParamsBuilder()
|
||||||
|
@ -166,7 +161,6 @@ public final class BinderChannelSmokeTest {
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
channel.shutdownNow();
|
channel.shutdownNow();
|
||||||
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider);
|
|
||||||
HostServices.awaitServiceShutdown();
|
HostServices.awaitServiceShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +229,11 @@ public final class BinderChannelSmokeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConnectViaTargetUri() throws Exception {
|
public void testConnectViaTargetUri() throws Exception {
|
||||||
channel = BinderChannelBuilder.forTarget(SERVER_TARGET_URI, appContext).build();
|
// Compare with the <intent-filter> mapping in AndroidManifest.xml.
|
||||||
|
channel =
|
||||||
|
BinderChannelBuilder.forTarget(
|
||||||
|
"intent://authority/path#Intent;action=action1;scheme=scheme;end;", appContext)
|
||||||
|
.build();
|
||||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +243,10 @@ public final class BinderChannelSmokeTest {
|
||||||
channel =
|
channel =
|
||||||
BinderChannelBuilder.forAddress(
|
BinderChannelBuilder.forAddress(
|
||||||
AndroidComponentAddress.forBindIntent(
|
AndroidComponentAddress.forBindIntent(
|
||||||
new Intent().setAction("action1").setPackage(appContext.getPackageName())),
|
new Intent()
|
||||||
|
.setAction("action1")
|
||||||
|
.setData(Uri.parse("scheme://authority/path"))
|
||||||
|
.setPackage(appContext.getPackageName())),
|
||||||
appContext)
|
appContext)
|
||||||
.build();
|
.build();
|
||||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package io.grpc.binder.internal;
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.DeadObjectException;
|
import android.os.DeadObjectException;
|
||||||
|
@ -24,9 +25,8 @@ import android.os.Parcel;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
import com.google.protobuf.Empty;
|
import com.google.protobuf.Empty;
|
||||||
import io.grpc.CallOptions;
|
import io.grpc.CallOptions;
|
||||||
|
@ -38,13 +38,13 @@ import io.grpc.ServerServiceDefinition;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.Status.Code;
|
import io.grpc.Status.Code;
|
||||||
import io.grpc.binder.AndroidComponentAddress;
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
import io.grpc.binder.AsyncSecurityPolicy;
|
|
||||||
import io.grpc.binder.BinderServerBuilder;
|
import io.grpc.binder.BinderServerBuilder;
|
||||||
import io.grpc.binder.HostServices;
|
import io.grpc.binder.HostServices;
|
||||||
import io.grpc.binder.SecurityPolicy;
|
import io.grpc.binder.SecurityPolicy;
|
||||||
import io.grpc.binder.internal.OneWayBinderProxies.BlackHoleOneWayBinderProxy;
|
import io.grpc.binder.internal.OneWayBinderProxies.BlackHoleOneWayBinderProxy;
|
||||||
import io.grpc.binder.internal.OneWayBinderProxies.BlockingBinderDecorator;
|
import io.grpc.binder.internal.OneWayBinderProxies.BlockingBinderDecorator;
|
||||||
import io.grpc.binder.internal.OneWayBinderProxies.ThrowingOneWayBinderProxy;
|
import io.grpc.binder.internal.OneWayBinderProxies.ThrowingOneWayBinderProxy;
|
||||||
|
import io.grpc.binder.internal.SettableAsyncSecurityPolicy.AuthRequest;
|
||||||
import io.grpc.internal.ClientStream;
|
import io.grpc.internal.ClientStream;
|
||||||
import io.grpc.internal.ClientStreamListener;
|
import io.grpc.internal.ClientStreamListener;
|
||||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||||
|
@ -63,7 +63,6 @@ import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -101,7 +100,7 @@ public final class BinderClientTransportTest {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
AndroidComponentAddress serverAddress;
|
AndroidComponentAddress serverAddress;
|
||||||
BinderTransport.BinderClientTransport transport;
|
BinderClientTransport transport;
|
||||||
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
|
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
|
||||||
|
|
||||||
private final ObjectPool<ScheduledExecutorService> executorServicePool =
|
private final ObjectPool<ScheduledExecutorService> executorServicePool =
|
||||||
|
@ -154,23 +153,32 @@ public final class BinderClientTransportTest {
|
||||||
.setScheduledExecutorPool(executorServicePool)
|
.setScheduledExecutorPool(executorServicePool)
|
||||||
.setOffloadExecutorPool(offloadServicePool);
|
.setOffloadExecutorPool(offloadServicePool);
|
||||||
|
|
||||||
|
@CanIgnoreReturnValue
|
||||||
public BinderClientTransportBuilder setSecurityPolicy(SecurityPolicy securityPolicy) {
|
public BinderClientTransportBuilder setSecurityPolicy(SecurityPolicy securityPolicy) {
|
||||||
factoryBuilder.setSecurityPolicy(securityPolicy);
|
factoryBuilder.setSecurityPolicy(securityPolicy);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CanIgnoreReturnValue
|
||||||
public BinderClientTransportBuilder setBinderDecorator(
|
public BinderClientTransportBuilder setBinderDecorator(
|
||||||
OneWayBinderProxy.Decorator binderDecorator) {
|
OneWayBinderProxy.Decorator binderDecorator) {
|
||||||
factoryBuilder.setBinderDecorator(binderDecorator);
|
factoryBuilder.setBinderDecorator(binderDecorator);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CanIgnoreReturnValue
|
||||||
public BinderClientTransportBuilder setReadyTimeoutMillis(int timeoutMillis) {
|
public BinderClientTransportBuilder setReadyTimeoutMillis(int timeoutMillis) {
|
||||||
factoryBuilder.setReadyTimeoutMillis(timeoutMillis);
|
factoryBuilder.setReadyTimeoutMillis(timeoutMillis);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinderTransport.BinderClientTransport build() {
|
@CanIgnoreReturnValue
|
||||||
|
public BinderClientTransportBuilder setPreAuthorizeServer(boolean preAuthorizeServer) {
|
||||||
|
factoryBuilder.setPreAuthorizeServers(preAuthorizeServer);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinderClientTransport build() {
|
||||||
return factoryBuilder
|
return factoryBuilder
|
||||||
.buildClientTransportFactory()
|
.buildClientTransportFactory()
|
||||||
.newClientTransport(serverAddress, new ClientTransportOptions(), null);
|
.newClientTransport(serverAddress, new ClientTransportOptions(), null);
|
||||||
|
@ -189,7 +197,7 @@ public final class BinderClientTransportTest {
|
||||||
private static void shutdownAndTerminate(ExecutorService executorService)
|
private static void shutdownAndTerminate(ExecutorService executorService)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
executorService.shutdownNow();
|
executorService.shutdownNow();
|
||||||
if (!executorService.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
if (!executorService.awaitTermination(TIMEOUT_SECONDS, SECONDS)) {
|
||||||
throw new AssertionError("executor failed to terminate promptly");
|
throw new AssertionError("executor failed to terminate promptly");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,27 +378,58 @@ public final class BinderClientTransportTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBlackHoleSecurityPolicyConnectTimeout() throws Exception {
|
public void testBlackHoleSecurityPolicyAuthTimeout() throws Exception {
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
transport =
|
transport =
|
||||||
new BinderClientTransportBuilder()
|
new BinderClientTransportBuilder()
|
||||||
.setSecurityPolicy(blockingSecurityPolicy)
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.setPreAuthorizeServer(false)
|
||||||
.setReadyTimeoutMillis(1_234)
|
.setReadyTimeoutMillis(1_234)
|
||||||
.build();
|
.build();
|
||||||
transport.start(transportListener).run();
|
transport.start(transportListener).run();
|
||||||
|
// Take the next authRequest but don't respond to it, in order to trigger the ready timeout.
|
||||||
|
AuthRequest authRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS);
|
||||||
|
|
||||||
Status transportStatus = transportListener.awaitShutdown();
|
Status transportStatus = transportListener.awaitShutdown();
|
||||||
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
|
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
|
||||||
assertThat(transportStatus.getDescription()).contains("1234");
|
assertThat(transportStatus.getDescription()).contains("1234");
|
||||||
transportListener.awaitTermination();
|
transportListener.awaitTermination();
|
||||||
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
|
// If the transport gave up waiting on auth, it should cancel its request.
|
||||||
|
assertThat(authRequest.isCancelled()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncSecurityPolicyFailure() throws Exception {
|
public void testBlackHoleSecurityPolicyPreAuthTimeout() throws Exception {
|
||||||
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
|
transport =
|
||||||
RuntimeException exception = new NullPointerException();
|
new BinderClientTransportBuilder()
|
||||||
securityPolicy.setAuthorizationException(exception);
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.setPreAuthorizeServer(true)
|
||||||
|
.setReadyTimeoutMillis(1_234)
|
||||||
|
.build();
|
||||||
transport.start(transportListener).run();
|
transport.start(transportListener).run();
|
||||||
|
// Take the next authRequest but don't respond to it, in order to trigger the ready timeout.
|
||||||
|
AuthRequest preAuthRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS);
|
||||||
|
|
||||||
|
Status transportStatus = transportListener.awaitShutdown();
|
||||||
|
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
|
||||||
|
assertThat(transportStatus.getDescription()).contains("1234");
|
||||||
|
transportListener.awaitTermination();
|
||||||
|
// If the transport gave up waiting on auth, it should cancel its request.
|
||||||
|
assertThat(preAuthRequest.isCancelled()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncSecurityPolicyAuthFailure() throws Exception {
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
|
transport =
|
||||||
|
new BinderClientTransportBuilder()
|
||||||
|
.setPreAuthorizeServer(false)
|
||||||
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.build();
|
||||||
|
RuntimeException exception = new NullPointerException();
|
||||||
|
transport.start(transportListener).run();
|
||||||
|
securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS).setResult(exception);
|
||||||
Status transportStatus = transportListener.awaitShutdown();
|
Status transportStatus = transportListener.awaitShutdown();
|
||||||
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
|
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
|
||||||
assertThat(transportStatus.getCause()).isEqualTo(exception);
|
assertThat(transportStatus.getCause()).isEqualTo(exception);
|
||||||
|
@ -398,19 +437,72 @@ public final class BinderClientTransportTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncSecurityPolicySuccess() throws Exception {
|
public void testAsyncSecurityPolicyPreAuthFailure() throws Exception {
|
||||||
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
|
transport =
|
||||||
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED);
|
new BinderClientTransportBuilder()
|
||||||
|
.setPreAuthorizeServer(true)
|
||||||
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.build();
|
||||||
|
RuntimeException exception = new NullPointerException();
|
||||||
transport.start(transportListener).run();
|
transport.start(transportListener).run();
|
||||||
|
securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS).setResult(exception);
|
||||||
Status transportStatus = transportListener.awaitShutdown();
|
Status transportStatus = transportListener.awaitShutdown();
|
||||||
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
|
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
|
||||||
|
assertThat(transportStatus.getCause()).isEqualTo(exception);
|
||||||
transportListener.awaitTermination();
|
transportListener.awaitTermination();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncSecurityPolicyAuthSuccess() throws Exception {
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
|
transport =
|
||||||
|
new BinderClientTransportBuilder()
|
||||||
|
.setPreAuthorizeServer(false)
|
||||||
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.build();
|
||||||
|
transport.start(transportListener).run();
|
||||||
|
securityPolicy
|
||||||
|
.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS)
|
||||||
|
.setResult(Status.PERMISSION_DENIED.withDescription("xyzzy"));
|
||||||
|
Status transportStatus = transportListener.awaitShutdown();
|
||||||
|
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
|
||||||
|
assertThat(transportStatus.getDescription()).contains("xyzzy");
|
||||||
|
transportListener.awaitTermination();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncSecurityPolicyPreAuthSuccess() throws Exception {
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
|
transport =
|
||||||
|
new BinderClientTransportBuilder()
|
||||||
|
.setPreAuthorizeServer(true)
|
||||||
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.build();
|
||||||
|
transport.start(transportListener).run();
|
||||||
|
securityPolicy
|
||||||
|
.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS)
|
||||||
|
.setResult(Status.PERMISSION_DENIED.withDescription("xyzzy"));
|
||||||
|
Status transportStatus = transportListener.awaitShutdown();
|
||||||
|
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
|
||||||
|
assertThat(transportStatus.getDescription()).contains("xyzzy");
|
||||||
|
transportListener.awaitTermination();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncSecurityPolicyCancelledUponExternalTermination() throws Exception {
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
|
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
|
||||||
|
transport.start(transportListener).run();
|
||||||
|
AuthRequest authRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS);
|
||||||
|
transport.shutdownNow(Status.UNAVAILABLE); // 'authRequest' remains unanswered!
|
||||||
|
transportListener.awaitShutdown();
|
||||||
|
transportListener.awaitTermination();
|
||||||
|
assertThat(authRequest.isCancelled()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
private static void startAndAwaitReady(
|
private static void startAndAwaitReady(
|
||||||
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
|
BinderClientTransport transport, TestTransportListener transportListener) throws Exception {
|
||||||
throws Exception {
|
|
||||||
transport.start(transportListener).run();
|
transport.start(transportListener).run();
|
||||||
transportListener.awaitReady();
|
transportListener.awaitReady();
|
||||||
}
|
}
|
||||||
|
@ -429,7 +521,7 @@ public final class BinderClientTransportTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status awaitShutdown() throws Exception {
|
public Status awaitShutdown() throws Exception {
|
||||||
return shutdownStatus.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
return shutdownStatus.get(TIMEOUT_SECONDS, SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -440,7 +532,7 @@ public final class BinderClientTransportTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void awaitTermination() throws Exception {
|
public void awaitTermination() throws Exception {
|
||||||
isTerminated.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
isTerminated.get(TIMEOUT_SECONDS, SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -451,7 +543,7 @@ public final class BinderClientTransportTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void awaitReady() throws Exception {
|
public void awaitReady() throws Exception {
|
||||||
isReady.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
isReady.get(TIMEOUT_SECONDS, SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -567,25 +659,4 @@ public final class BinderClientTransportTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An AsyncSecurityPolicy that lets a test specify the outcome of checkAuthorizationAsync(). */
|
|
||||||
static class SettableAsyncSecurityPolicy extends AsyncSecurityPolicy {
|
|
||||||
private SettableFuture<Status> result = SettableFuture.create();
|
|
||||||
|
|
||||||
public void clearAuthorizationResult() {
|
|
||||||
result = SettableFuture.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setAuthorizationResult(Status status) {
|
|
||||||
return result.set(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setAuthorizationException(Throwable t) {
|
|
||||||
return result.setException(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
|
|
||||||
return Futures.nonCancellationPropagating(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,8 +106,7 @@ public final class BinderTransportTest extends AbstractTransportTest {
|
||||||
options.setEagAttributes(eagAttrs());
|
options.setEagAttributes(eagAttrs());
|
||||||
options.setChannelLogger(transportLogger());
|
options.setChannelLogger(transportLogger());
|
||||||
|
|
||||||
return new BinderTransport.BinderClientTransport(
|
return new BinderClientTransport(builder.buildClientTransportFactory(), addr, options);
|
||||||
builder.buildClientTransportFactory(), addr, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -58,7 +58,7 @@ public final class AndroidComponentAddress extends SocketAddress {
|
||||||
@Nullable
|
@Nullable
|
||||||
private final UserHandle targetUser; // null means the same user that hosts this process.
|
private final UserHandle targetUser; // null means the same user that hosts this process.
|
||||||
|
|
||||||
protected AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
|
private AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
bindIntent.getComponent() != null || bindIntent.getPackage() != null,
|
bindIntent.getComponent() != null || bindIntent.getPackage() != null,
|
||||||
"'bindIntent' must be explicit. Specify either a package or ComponentName.");
|
"'bindIntent' must be explicit. Specify either a package or ComponentName.");
|
||||||
|
@ -250,7 +250,22 @@ public final class AndroidComponentAddress extends SocketAddress {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** See {@link AndroidComponentAddress#getTargetUser()}. */
|
/**
|
||||||
|
* Specifies the Android user in which the built Address' bind Intent will be evaluated.
|
||||||
|
*
|
||||||
|
* <p>Connecting to a server in a different Android user is uncommon and requires the client app
|
||||||
|
* have runtime visibility of @SystemApi's and hold certain @SystemApi permissions.
|
||||||
|
* The device must also be running Android SDK version 30 or higher.
|
||||||
|
*
|
||||||
|
* <p>See https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces
|
||||||
|
* for details on which apps can call the underlying @SystemApi's needed to make this type
|
||||||
|
* of connection.
|
||||||
|
*
|
||||||
|
* <p>One of the "android.permission.INTERACT_ACROSS_XXX" permissions is required. The exact one
|
||||||
|
* depends on the calling user's relationship to the target user, whether client and server are
|
||||||
|
* in the same or different apps, and the version of Android in use. See {@link
|
||||||
|
* Context#bindServiceAsUser}, the essential underlying Android API, for details.
|
||||||
|
*/
|
||||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
|
||||||
public Builder setTargetUser(@Nullable UserHandle targetUser) {
|
public Builder setTargetUser(@Nullable UserHandle targetUser) {
|
||||||
this.targetUser = targetUser;
|
this.targetUser = targetUser;
|
||||||
|
|
|
@ -18,6 +18,8 @@ package io.grpc.binder;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
|
import io.grpc.Attributes;
|
||||||
|
import io.grpc.EquivalentAddressGroup;
|
||||||
import io.grpc.ExperimentalApi;
|
import io.grpc.ExperimentalApi;
|
||||||
import io.grpc.NameResolver;
|
import io.grpc.NameResolver;
|
||||||
|
|
||||||
|
@ -32,15 +34,42 @@ public final class ApiConstants {
|
||||||
*/
|
*/
|
||||||
public static final String ACTION_BIND = "grpc.io.action.BIND";
|
public static final String ACTION_BIND = "grpc.io.action.BIND";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives a {@link NameResolver} access to its Channel's "source" {@link android.content.Context},
|
||||||
|
* the entry point to almost every other Android API.
|
||||||
|
*
|
||||||
|
* <p>This argument is set automatically by {@link BinderChannelBuilder}. Any value passed to
|
||||||
|
* {@link io.grpc.ManagedChannelBuilder#setNameResolverArg} will be ignored.
|
||||||
|
*
|
||||||
|
* <p>See {@link BinderChannelBuilder#forTarget(String, android.content.Context)} for more.
|
||||||
|
*/
|
||||||
|
public static final NameResolver.Args.Key<android.content.Context> SOURCE_ANDROID_CONTEXT =
|
||||||
|
NameResolver.Args.Key.create("source-android-context");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the Android user in which target URIs should be resolved.
|
* Specifies the Android user in which target URIs should be resolved.
|
||||||
*
|
*
|
||||||
* <p>{@link UserHandle} can't reasonably be encoded in a target URI string. Instead, all
|
* <p>{@link UserHandle} can't reasonably be encoded in a target URI string. Instead, all {@link
|
||||||
* {@link io.grpc.NameResolverProvider}s producing {@link AndroidComponentAddress}es should let
|
* io.grpc.NameResolverProvider}s producing {@link AndroidComponentAddress}es should let clients
|
||||||
* clients address servers in another Android user using this argument.
|
* address servers in another Android user using this argument.
|
||||||
*
|
*
|
||||||
* <p>See also {@link AndroidComponentAddress#getTargetUser()}.
|
* <p>Connecting to a server in a different Android user is uncommon and can only be done by a
|
||||||
|
* "system app" client with special permissions. See {@link
|
||||||
|
* AndroidComponentAddress.Builder#setTargetUser(UserHandle)} for details.
|
||||||
*/
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
|
||||||
public static final NameResolver.Args.Key<UserHandle> TARGET_ANDROID_USER =
|
public static final NameResolver.Args.Key<UserHandle> TARGET_ANDROID_USER =
|
||||||
NameResolver.Args.Key.create("target-android-user");
|
NameResolver.Args.Key.create("target-android-user");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lets you override a Channel's pre-auth configuration (see {@link
|
||||||
|
* BinderChannelBuilder#preAuthorizeServers(boolean)}) for a given {@link EquivalentAddressGroup}.
|
||||||
|
*
|
||||||
|
* <p>A {@link NameResolver} that discovers servers from an untrusted source like PackageManager
|
||||||
|
* can use this to force server pre-auth and prevent abuse.
|
||||||
|
*/
|
||||||
|
@EquivalentAddressGroup.Attr
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12191")
|
||||||
|
public static final Attributes.Key<Boolean> PRE_AUTH_SERVER_OVERRIDE =
|
||||||
|
Attributes.Key.create("pre-auth-server-override");
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,4 +67,25 @@ public abstract class AsyncSecurityPolicy extends SecurityPolicy {
|
||||||
* authorized.
|
* authorized.
|
||||||
*/
|
*/
|
||||||
public abstract ListenableFuture<Status> checkAuthorizationAsync(int uid);
|
public abstract ListenableFuture<Status> checkAuthorizationAsync(int uid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether the given Android UID is authorized, without providing its raw integer value.
|
||||||
|
*
|
||||||
|
* <p>Calling this is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
|
||||||
|
* the caller provides a {@link PeerUid} wrapper instead of the raw integer uid (known only to the
|
||||||
|
* transport). This allows a server to check additional application-layer security policy for
|
||||||
|
* itself *after* the call itself is authorized by the transport layer. Cross cutting application-
|
||||||
|
* layer checks could be done from a {@link io.grpc.ServerInterceptor}. Checks based on the
|
||||||
|
* substance of a request message could be done by the individual RPC method implementations
|
||||||
|
* themselves.
|
||||||
|
*
|
||||||
|
* <p>See #checkAuthorizationAsync(int) for details on the semantics. See {@link
|
||||||
|
* PeerUids#newPeerIdentifyingServerInterceptor()} for how to get a {@link PeerUid}.
|
||||||
|
*
|
||||||
|
* @param uid The Android UID to authenticate.
|
||||||
|
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||||
|
*/
|
||||||
|
public final ListenableFuture<Status> checkAuthorizationAsync(PeerUid uid) {
|
||||||
|
return checkAuthorizationAsync(uid.getUid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,9 +242,9 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
||||||
* specify a {@link UserHandle}. If neither the Channel nor the {@link AndroidComponentAddress}
|
* specify a {@link UserHandle}. If neither the Channel nor the {@link AndroidComponentAddress}
|
||||||
* specifies a target user, the {@link UserHandle} of the current process will be used.
|
* specifies a target user, the {@link UserHandle} of the current process will be used.
|
||||||
*
|
*
|
||||||
* <p>Targeting a Service in a different Android user is uncommon and requires special permissions
|
* <p>Connecting to a server in a different Android user is uncommon and can only be done by a
|
||||||
* normally reserved for system apps. See {@link android.content.Context#bindServiceAsUser} for
|
* "system app" client with special permissions. See {@link
|
||||||
* details.
|
* AndroidComponentAddress.Builder#setTargetUser(UserHandle)} for details.
|
||||||
*
|
*
|
||||||
* @deprecated This method's name is misleading because it implies an impersonated client identity
|
* @deprecated This method's name is misleading because it implies an impersonated client identity
|
||||||
* when it's actually specifying part of the server's location. It's also no longer necessary
|
* when it's actually specifying part of the server's location. It's also no longer necessary
|
||||||
|
@ -279,6 +279,35 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks servers against this Channel's {@link SecurityPolicy} *before* binding.
|
||||||
|
*
|
||||||
|
* <p>Android users can be tricked into installing a malicious app with the same package name as a
|
||||||
|
* legitimate server. That's why we don't send calls to a server until it has been authorized by
|
||||||
|
* an appropriate {@link SecurityPolicy}. But merely binding to a malicious server can enable
|
||||||
|
* "keep-alive" and "background activity launch" abuse, even if it's ultimately unauthorized.
|
||||||
|
* Pre-authorization mitigates these threats by performing a preliminary {@link SecurityPolicy}
|
||||||
|
* check against a server app's PackageManager-registered identity without actually creating an
|
||||||
|
* instance of it. This is especially important for security when the server's direct address
|
||||||
|
* isn't known in advance but rather resolved via target URI or discovered by other means.
|
||||||
|
*
|
||||||
|
* <p>Note that, unlike ordinary authorization, pre-authorization is performed against the server
|
||||||
|
* app's UID, not the UID of the process hosting the bound Service. These can be different, most
|
||||||
|
* commonly due to services that set `android:isolatedProcess=true`.
|
||||||
|
*
|
||||||
|
* <p>Pre-authorization is strongly recommended but it remains optional for now because of this
|
||||||
|
* behavior change and the small performance cost.
|
||||||
|
*
|
||||||
|
* <p>The default value of this property is false but it will become true in a future release.
|
||||||
|
* Clients that require a particular behavior should configure it explicitly using this method
|
||||||
|
* rather than relying on the default.
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12191")
|
||||||
|
public BinderChannelBuilder preAuthorizeServers(boolean preAuthorize) {
|
||||||
|
transportFactoryBuilder.setPreAuthorizeServers(preAuthorize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BinderChannelBuilder idleTimeout(long value, TimeUnit unit) {
|
public BinderChannelBuilder idleTimeout(long value, TimeUnit unit) {
|
||||||
checkState(
|
checkState(
|
||||||
|
@ -292,6 +321,8 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
||||||
public ManagedChannel build() {
|
public ManagedChannel build() {
|
||||||
transportFactoryBuilder.setOffloadExecutorPool(
|
transportFactoryBuilder.setOffloadExecutorPool(
|
||||||
managedChannelImplBuilder.getOffloadExecutorPool());
|
managedChannelImplBuilder.getOffloadExecutorPool());
|
||||||
|
setNameResolverArg(
|
||||||
|
ApiConstants.SOURCE_ANDROID_CONTEXT, transportFactoryBuilder.getSourceContext());
|
||||||
return super.build();
|
return super.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,6 @@ public final class SecurityPolicies {
|
||||||
* Creates {@link SecurityPolicy} which checks if the app is a device owner app. See {@link
|
* Creates {@link SecurityPolicy} which checks if the app is a device owner app. See {@link
|
||||||
* DevicePolicyManager}.
|
* DevicePolicyManager}.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(18)
|
|
||||||
public static io.grpc.binder.SecurityPolicy isDeviceOwner(Context applicationContext) {
|
public static io.grpc.binder.SecurityPolicy isDeviceOwner(Context applicationContext) {
|
||||||
DevicePolicyManager devicePolicyManager =
|
DevicePolicyManager devicePolicyManager =
|
||||||
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||||
|
@ -199,7 +198,6 @@ public final class SecurityPolicies {
|
||||||
* Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See {@link
|
* Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See {@link
|
||||||
* DevicePolicyManager}.
|
* DevicePolicyManager}.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(21)
|
|
||||||
public static SecurityPolicy isProfileOwner(Context applicationContext) {
|
public static SecurityPolicy isProfileOwner(Context applicationContext) {
|
||||||
DevicePolicyManager devicePolicyManager =
|
DevicePolicyManager devicePolicyManager =
|
||||||
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||||
|
|
|
@ -53,4 +53,25 @@ public abstract class SecurityPolicy {
|
||||||
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||||
*/
|
*/
|
||||||
public abstract Status checkAuthorization(int uid);
|
public abstract Status checkAuthorization(int uid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether the given Android UID is authorized, without providing its raw integer value.
|
||||||
|
*
|
||||||
|
* <p>Calling this is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
|
||||||
|
* the caller provides a {@link PeerUid} wrapper instead of the raw integer uid (known only to the
|
||||||
|
* transport). This allows a server to check additional application-layer security policy for
|
||||||
|
* itself *after* the call itself is authorized by the transport layer. Cross cutting application-
|
||||||
|
* layer checks could be done from a {@link io.grpc.ServerInterceptor}. Checks based on the
|
||||||
|
* substance of a request message could be done by the individual RPC method implementations
|
||||||
|
* themselves.
|
||||||
|
*
|
||||||
|
* <p>See #checkAuthorizationAsync(int) for details on the semantics. See {@link
|
||||||
|
* PeerUids#newPeerIdentifyingServerInterceptor()} for how to get a {@link PeerUid}.
|
||||||
|
*
|
||||||
|
* @param uid The Android UID to authenticate.
|
||||||
|
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||||
|
*/
|
||||||
|
public final Status checkAuthorization(PeerUid uid) {
|
||||||
|
return checkAuthorization(uid.getUid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import io.grpc.internal.ServerTransport;
|
||||||
import io.grpc.internal.ServerTransportListener;
|
import io.grpc.internal.ServerTransportListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks which {@link BinderTransport.BinderServerTransport} are currently active and allows
|
* Tracks which {@link BinderServerTransport} are currently active and allows invoking a {@link
|
||||||
* invoking a {@link Runnable} only once all transports are terminated.
|
* Runnable} only once all transports are terminated.
|
||||||
*/
|
*/
|
||||||
final class ActiveTransportTracker implements ServerListener {
|
final class ActiveTransportTracker implements ServerListener {
|
||||||
private final ServerListener delegate;
|
private final ServerListener delegate;
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
|
|
||||||
package io.grpc.binder.internal;
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import androidx.annotation.AnyThread;
|
import androidx.annotation.AnyThread;
|
||||||
import androidx.annotation.MainThread;
|
import androidx.annotation.MainThread;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
|
import io.grpc.StatusException;
|
||||||
|
|
||||||
/** An interface for managing a {@code Binder} connection. */
|
/** An interface for managing a {@code Binder} connection. */
|
||||||
interface Bindable {
|
interface Bindable {
|
||||||
|
@ -45,6 +47,19 @@ interface Bindable {
|
||||||
void onUnbound(Status reason);
|
void onUnbound(Status reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches details about the remote Service from PackageManager without binding to it.
|
||||||
|
*
|
||||||
|
* <p>Resolving an untrusted address before binding to it lets you screen out problematic servers
|
||||||
|
* before giving them a chance to run. However, note that the identity/existence of the resolved
|
||||||
|
* Service can change between the time this method returns and the time you actually bind/connect
|
||||||
|
* to it. For example, suppose the target package gets uninstalled or upgraded right after this
|
||||||
|
* method returns. In {@link Observer#onBound}, you should verify that the server you resolved is
|
||||||
|
* the same one you connected to.
|
||||||
|
*/
|
||||||
|
@AnyThread
|
||||||
|
ServiceInfo resolve() throws StatusException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to bind with the remote service.
|
* Attempt to bind with the remote service.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,441 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static io.grpc.binder.ApiConstants.PRE_AUTH_SERVER_OVERRIDE;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Process;
|
||||||
|
import com.google.common.base.Ticker;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.errorprone.annotations.CheckReturnValue;
|
||||||
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
|
import io.grpc.Attributes;
|
||||||
|
import io.grpc.CallOptions;
|
||||||
|
import io.grpc.ClientStreamTracer;
|
||||||
|
import io.grpc.Grpc;
|
||||||
|
import io.grpc.Internal;
|
||||||
|
import io.grpc.InternalLogId;
|
||||||
|
import io.grpc.Metadata;
|
||||||
|
import io.grpc.MethodDescriptor;
|
||||||
|
import io.grpc.SecurityLevel;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.StatusException;
|
||||||
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import io.grpc.binder.AsyncSecurityPolicy;
|
||||||
|
import io.grpc.binder.InboundParcelablePolicy;
|
||||||
|
import io.grpc.binder.SecurityPolicy;
|
||||||
|
import io.grpc.internal.ClientStream;
|
||||||
|
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||||
|
import io.grpc.internal.ConnectionClientTransport;
|
||||||
|
import io.grpc.internal.FailingClientStream;
|
||||||
|
import io.grpc.internal.GrpcAttributes;
|
||||||
|
import io.grpc.internal.GrpcUtil;
|
||||||
|
import io.grpc.internal.ManagedClientTransport;
|
||||||
|
import io.grpc.internal.ObjectPool;
|
||||||
|
import io.grpc.internal.StatsTraceContext;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/** Concrete client-side transport implementation. */
|
||||||
|
@ThreadSafe
|
||||||
|
@Internal
|
||||||
|
public final class BinderClientTransport extends BinderTransport
|
||||||
|
implements ConnectionClientTransport, Bindable.Observer {
|
||||||
|
|
||||||
|
private final ObjectPool<? extends Executor> offloadExecutorPool;
|
||||||
|
private final Executor offloadExecutor;
|
||||||
|
private final SecurityPolicy securityPolicy;
|
||||||
|
private final Bindable serviceBinding;
|
||||||
|
|
||||||
|
/** Number of ongoing calls which keep this transport "in-use". */
|
||||||
|
private final AtomicInteger numInUseStreams;
|
||||||
|
|
||||||
|
private final long readyTimeoutMillis;
|
||||||
|
private final PingTracker pingTracker;
|
||||||
|
private final boolean preAuthorizeServer;
|
||||||
|
|
||||||
|
@Nullable private ManagedClientTransport.Listener clientTransportListener;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private int latestCallId = FIRST_CALL_ID;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private ScheduledFuture<?> readyTimeoutFuture; // != null iff timeout scheduled.
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
@Nullable
|
||||||
|
private ListenableFuture<Status> authResultFuture; // null before we check auth.
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
@Nullable
|
||||||
|
private ListenableFuture<Status> preAuthResultFuture; // null before we pre-auth.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new transport instance.
|
||||||
|
*
|
||||||
|
* @param factory parameters common to all a Channel's transports
|
||||||
|
* @param targetAddress the fully resolved and load-balanced server address
|
||||||
|
* @param options other parameters that can vary as transports come and go within a Channel
|
||||||
|
*/
|
||||||
|
public BinderClientTransport(
|
||||||
|
BinderClientTransportFactory factory,
|
||||||
|
AndroidComponentAddress targetAddress,
|
||||||
|
ClientTransportOptions options) {
|
||||||
|
super(
|
||||||
|
factory.scheduledExecutorPool,
|
||||||
|
buildClientAttributes(
|
||||||
|
options.getEagAttributes(),
|
||||||
|
factory.sourceContext,
|
||||||
|
targetAddress,
|
||||||
|
factory.inboundParcelablePolicy),
|
||||||
|
factory.binderDecorator,
|
||||||
|
buildLogId(factory.sourceContext, targetAddress));
|
||||||
|
this.offloadExecutorPool = factory.offloadExecutorPool;
|
||||||
|
this.securityPolicy = factory.securityPolicy;
|
||||||
|
this.offloadExecutor = offloadExecutorPool.getObject();
|
||||||
|
this.readyTimeoutMillis = factory.readyTimeoutMillis;
|
||||||
|
Boolean preAuthServerOverride = options.getEagAttributes().get(PRE_AUTH_SERVER_OVERRIDE);
|
||||||
|
this.preAuthorizeServer =
|
||||||
|
preAuthServerOverride != null ? preAuthServerOverride : factory.preAuthorizeServers;
|
||||||
|
numInUseStreams = new AtomicInteger();
|
||||||
|
pingTracker = new PingTracker(Ticker.systemTicker(), (id) -> sendPing(id));
|
||||||
|
|
||||||
|
serviceBinding =
|
||||||
|
new ServiceBinding(
|
||||||
|
factory.mainThreadExecutor,
|
||||||
|
factory.sourceContext,
|
||||||
|
factory.channelCredentials,
|
||||||
|
targetAddress.asBindIntent(),
|
||||||
|
targetAddress.getTargetUser() != null
|
||||||
|
? targetAddress.getTargetUser()
|
||||||
|
: factory.defaultTargetUserHandle,
|
||||||
|
factory.bindServiceFlags.toInteger(),
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void releaseExecutors() {
|
||||||
|
super.releaseExecutors();
|
||||||
|
offloadExecutorPool.returnObject(offloadExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void onBound(IBinder binder) {
|
||||||
|
sendSetupTransaction(binderDecorator.decorate(OneWayBinderProxy.wrap(binder, offloadExecutor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void onUnbound(Status reason) {
|
||||||
|
shutdownInternal(reason, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
@Override
|
||||||
|
public synchronized Runnable start(Listener clientTransportListener) {
|
||||||
|
this.clientTransportListener = checkNotNull(clientTransportListener);
|
||||||
|
return () -> {
|
||||||
|
synchronized (BinderClientTransport.this) {
|
||||||
|
if (inState(TransportState.NOT_STARTED)) {
|
||||||
|
setState(TransportState.SETUP);
|
||||||
|
try {
|
||||||
|
if (preAuthorizeServer) {
|
||||||
|
preAuthorize(serviceBinding.resolve());
|
||||||
|
} else {
|
||||||
|
serviceBinding.bind();
|
||||||
|
}
|
||||||
|
} catch (StatusException e) {
|
||||||
|
shutdownInternal(e.getStatus(), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (readyTimeoutMillis >= 0) {
|
||||||
|
readyTimeoutFuture =
|
||||||
|
getScheduledExecutorService()
|
||||||
|
.schedule(
|
||||||
|
BinderClientTransport.this::onReadyTimeout,
|
||||||
|
readyTimeoutMillis,
|
||||||
|
MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private void preAuthorize(ServiceInfo serviceInfo) {
|
||||||
|
// It's unlikely, but the identity/existence of this Service could change by the time we
|
||||||
|
// actually connect. It doesn't matter though, because:
|
||||||
|
// - If pre-auth fails (but would succeed against the server's new state), the grpc-core layer
|
||||||
|
// will eventually retry using a new transport instance that will see the Service's new state.
|
||||||
|
// - If pre-auth succeeds (but would fail against the server's new state), we might give an
|
||||||
|
// unauthorized server a chance to run, but the connection will still fail by SecurityPolicy
|
||||||
|
// check later in handshake. Pre-auth remains effective at mitigating abuse because malware
|
||||||
|
// can't typically control the exact timing of its installation.
|
||||||
|
preAuthResultFuture = checkServerAuthorizationAsync(serviceInfo.applicationInfo.uid);
|
||||||
|
Futures.addCallback(
|
||||||
|
preAuthResultFuture,
|
||||||
|
new FutureCallback<Status>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Status result) {
|
||||||
|
handlePreAuthResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
handleAuthResult(t);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
offloadExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void handlePreAuthResult(Status authorization) {
|
||||||
|
if (inState(TransportState.SETUP)) {
|
||||||
|
if (!authorization.isOk()) {
|
||||||
|
shutdownInternal(authorization, true);
|
||||||
|
} else {
|
||||||
|
serviceBinding.bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void onReadyTimeout() {
|
||||||
|
if (inState(TransportState.SETUP)) {
|
||||||
|
readyTimeoutFuture = null;
|
||||||
|
shutdownInternal(
|
||||||
|
Status.DEADLINE_EXCEEDED.withDescription(
|
||||||
|
"Connect timeout " + readyTimeoutMillis + "ms lapsed"),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized ClientStream newStream(
|
||||||
|
final MethodDescriptor<?, ?> method,
|
||||||
|
final Metadata headers,
|
||||||
|
final CallOptions callOptions,
|
||||||
|
ClientStreamTracer[] tracers) {
|
||||||
|
if (!inState(TransportState.READY)) {
|
||||||
|
return newFailingClientStream(
|
||||||
|
isShutdown()
|
||||||
|
? shutdownStatus
|
||||||
|
: Status.INTERNAL.withDescription("newStream() before transportReady()"),
|
||||||
|
attributes,
|
||||||
|
headers,
|
||||||
|
tracers);
|
||||||
|
}
|
||||||
|
|
||||||
|
int callId = latestCallId++;
|
||||||
|
if (latestCallId == LAST_CALL_ID) {
|
||||||
|
latestCallId = FIRST_CALL_ID;
|
||||||
|
}
|
||||||
|
StatsTraceContext statsTraceContext =
|
||||||
|
StatsTraceContext.newClientContext(tracers, attributes, headers);
|
||||||
|
Inbound.ClientInbound inbound =
|
||||||
|
new Inbound.ClientInbound(
|
||||||
|
this, attributes, callId, GrpcUtil.shouldBeCountedForInUse(callOptions));
|
||||||
|
if (ongoingCalls.putIfAbsent(callId, inbound) != null) {
|
||||||
|
Status failure = Status.INTERNAL.withDescription("Clashing call IDs");
|
||||||
|
shutdownInternal(failure, true);
|
||||||
|
return newFailingClientStream(failure, attributes, headers, tracers);
|
||||||
|
} else {
|
||||||
|
if (inbound.countsForInUse() && numInUseStreams.getAndIncrement() == 0) {
|
||||||
|
clientTransportListener.transportInUse(true);
|
||||||
|
}
|
||||||
|
Outbound.ClientOutbound outbound =
|
||||||
|
new Outbound.ClientOutbound(this, callId, method, headers, statsTraceContext);
|
||||||
|
if (method.getType().clientSendsOneMessage()) {
|
||||||
|
return new SingleMessageClientStream(inbound, outbound, attributes);
|
||||||
|
} else {
|
||||||
|
return new MultiMessageClientStream(inbound, outbound, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void unregisterInbound(Inbound<?> inbound) {
|
||||||
|
if (inbound.countsForInUse() && numInUseStreams.decrementAndGet() == 0) {
|
||||||
|
clientTransportListener.transportInUse(false);
|
||||||
|
}
|
||||||
|
super.unregisterInbound(inbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ping(final PingCallback callback, Executor executor) {
|
||||||
|
pingTracker.startPing(callback, executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void shutdown(Status reason) {
|
||||||
|
checkNotNull(reason, "reason");
|
||||||
|
shutdownInternal(reason, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void shutdownNow(Status reason) {
|
||||||
|
checkNotNull(reason, "reason");
|
||||||
|
shutdownInternal(reason, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GuardedBy("this")
|
||||||
|
void notifyShutdown(Status status) {
|
||||||
|
clientTransportListener.transportShutdown(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GuardedBy("this")
|
||||||
|
void notifyTerminated() {
|
||||||
|
if (numInUseStreams.getAndSet(0) > 0) {
|
||||||
|
clientTransportListener.transportInUse(false);
|
||||||
|
}
|
||||||
|
if (readyTimeoutFuture != null) {
|
||||||
|
readyTimeoutFuture.cancel(false);
|
||||||
|
readyTimeoutFuture = null;
|
||||||
|
}
|
||||||
|
if (preAuthResultFuture != null) {
|
||||||
|
preAuthResultFuture.cancel(false); // No effect if already complete.
|
||||||
|
}
|
||||||
|
if (authResultFuture != null) {
|
||||||
|
authResultFuture.cancel(false); // No effect if already complete.
|
||||||
|
}
|
||||||
|
serviceBinding.unbind();
|
||||||
|
clientTransportListener.transportTerminated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GuardedBy("this")
|
||||||
|
protected void handleSetupTransport(Parcel parcel) {
|
||||||
|
int remoteUid = Binder.getCallingUid();
|
||||||
|
attributes = setSecurityAttrs(attributes, remoteUid);
|
||||||
|
if (inState(TransportState.SETUP)) {
|
||||||
|
int version = parcel.readInt();
|
||||||
|
IBinder binder = parcel.readStrongBinder();
|
||||||
|
if (version != WIRE_FORMAT_VERSION) {
|
||||||
|
shutdownInternal(Status.UNAVAILABLE.withDescription("Wire format version mismatch"), true);
|
||||||
|
} else if (binder == null) {
|
||||||
|
shutdownInternal(
|
||||||
|
Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true);
|
||||||
|
} else {
|
||||||
|
authResultFuture = checkServerAuthorizationAsync(remoteUid);
|
||||||
|
Futures.addCallback(
|
||||||
|
authResultFuture,
|
||||||
|
new FutureCallback<Status>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Status result) {
|
||||||
|
handleAuthResult(binder, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
handleAuthResult(t);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
offloadExecutor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<Status> checkServerAuthorizationAsync(int remoteUid) {
|
||||||
|
return (securityPolicy instanceof AsyncSecurityPolicy)
|
||||||
|
? ((AsyncSecurityPolicy) securityPolicy).checkAuthorizationAsync(remoteUid)
|
||||||
|
: Futures.submit(() -> securityPolicy.checkAuthorization(remoteUid), offloadExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void handleAuthResult(IBinder binder, Status authorization) {
|
||||||
|
if (inState(TransportState.SETUP)) {
|
||||||
|
if (!authorization.isOk()) {
|
||||||
|
shutdownInternal(authorization, true);
|
||||||
|
} else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) {
|
||||||
|
shutdownInternal(
|
||||||
|
Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true);
|
||||||
|
} else {
|
||||||
|
// Check state again, since a failure inside setOutgoingBinder (or a callback it
|
||||||
|
// triggers), could have shut us down.
|
||||||
|
if (!isShutdown()) {
|
||||||
|
setState(TransportState.READY);
|
||||||
|
attributes = clientTransportListener.filterTransport(attributes);
|
||||||
|
clientTransportListener.transportReady();
|
||||||
|
if (readyTimeoutFuture != null) {
|
||||||
|
readyTimeoutFuture.cancel(false);
|
||||||
|
readyTimeoutFuture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void handleAuthResult(Throwable t) {
|
||||||
|
shutdownInternal(
|
||||||
|
Status.INTERNAL.withDescription("Could not evaluate SecurityPolicy").withCause(t), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
@Override
|
||||||
|
protected void handlePingResponse(Parcel parcel) {
|
||||||
|
pingTracker.onPingResponse(parcel.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClientStream newFailingClientStream(
|
||||||
|
Status failure, Attributes attributes, Metadata headers, ClientStreamTracer[] tracers) {
|
||||||
|
StatsTraceContext statsTraceContext =
|
||||||
|
StatsTraceContext.newClientContext(tracers, attributes, headers);
|
||||||
|
statsTraceContext.clientOutboundHeaders();
|
||||||
|
return new FailingClientStream(failure, tracers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InternalLogId buildLogId(
|
||||||
|
Context sourceContext, AndroidComponentAddress targetAddress) {
|
||||||
|
return InternalLogId.allocate(
|
||||||
|
BinderClientTransport.class,
|
||||||
|
sourceContext.getClass().getSimpleName() + "->" + targetAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Attributes buildClientAttributes(
|
||||||
|
Attributes eagAttrs,
|
||||||
|
Context sourceContext,
|
||||||
|
AndroidComponentAddress targetAddress,
|
||||||
|
InboundParcelablePolicy inboundParcelablePolicy) {
|
||||||
|
return Attributes.newBuilder()
|
||||||
|
.set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) // Trust noone for now.
|
||||||
|
.set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs)
|
||||||
|
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, AndroidComponentAddress.forContext(sourceContext))
|
||||||
|
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, targetAddress)
|
||||||
|
.set(INBOUND_PARCELABLE_POLICY, inboundParcelablePolicy)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Attributes setSecurityAttrs(Attributes attributes, int uid) {
|
||||||
|
return attributes.toBuilder()
|
||||||
|
.set(REMOTE_UID, uid)
|
||||||
|
.set(
|
||||||
|
GrpcAttributes.ATTR_SECURITY_LEVEL,
|
||||||
|
uid == Process.myUid()
|
||||||
|
? SecurityLevel.PRIVACY_AND_INTEGRITY
|
||||||
|
: SecurityLevel.INTEGRITY) // TODO: Have the SecrityPolicy decide this.
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,7 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
||||||
final InboundParcelablePolicy inboundParcelablePolicy;
|
final InboundParcelablePolicy inboundParcelablePolicy;
|
||||||
final OneWayBinderProxy.Decorator binderDecorator;
|
final OneWayBinderProxy.Decorator binderDecorator;
|
||||||
final long readyTimeoutMillis;
|
final long readyTimeoutMillis;
|
||||||
|
final boolean preAuthorizeServers; // TODO(jdcormie): Default to true.
|
||||||
|
|
||||||
ScheduledExecutorService executorService;
|
ScheduledExecutorService executorService;
|
||||||
Executor offloadExecutor;
|
Executor offloadExecutor;
|
||||||
|
@ -75,18 +76,19 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
||||||
inboundParcelablePolicy = checkNotNull(builder.inboundParcelablePolicy);
|
inboundParcelablePolicy = checkNotNull(builder.inboundParcelablePolicy);
|
||||||
binderDecorator = checkNotNull(builder.binderDecorator);
|
binderDecorator = checkNotNull(builder.binderDecorator);
|
||||||
readyTimeoutMillis = builder.readyTimeoutMillis;
|
readyTimeoutMillis = builder.readyTimeoutMillis;
|
||||||
|
preAuthorizeServers = builder.preAuthorizeServers;
|
||||||
|
|
||||||
executorService = scheduledExecutorPool.getObject();
|
executorService = scheduledExecutorPool.getObject();
|
||||||
offloadExecutor = offloadExecutorPool.getObject();
|
offloadExecutor = offloadExecutorPool.getObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BinderTransport.BinderClientTransport newClientTransport(
|
public BinderClientTransport newClientTransport(
|
||||||
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
|
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("The transport factory is closed.");
|
throw new IllegalStateException("The transport factory is closed.");
|
||||||
}
|
}
|
||||||
return new BinderTransport.BinderClientTransport(this, (AndroidComponentAddress) addr, options);
|
return new BinderClientTransport(this, (AndroidComponentAddress) addr, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -128,6 +130,7 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
||||||
InboundParcelablePolicy inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT;
|
InboundParcelablePolicy inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT;
|
||||||
OneWayBinderProxy.Decorator binderDecorator = OneWayBinderProxy.IDENTITY_DECORATOR;
|
OneWayBinderProxy.Decorator binderDecorator = OneWayBinderProxy.IDENTITY_DECORATOR;
|
||||||
long readyTimeoutMillis = 60_000;
|
long readyTimeoutMillis = 60_000;
|
||||||
|
boolean preAuthorizeServers;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BinderClientTransportFactory buildClientTransportFactory() {
|
public BinderClientTransportFactory buildClientTransportFactory() {
|
||||||
|
@ -139,6 +142,10 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Context getSourceContext() {
|
||||||
|
return sourceContext;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setOffloadExecutorPool(ObjectPool<? extends Executor> offloadExecutorPool) {
|
public Builder setOffloadExecutorPool(ObjectPool<? extends Executor> offloadExecutorPool) {
|
||||||
this.offloadExecutorPool = checkNotNull(offloadExecutorPool, "offloadExecutorPool");
|
this.offloadExecutorPool = checkNotNull(offloadExecutorPool, "offloadExecutorPool");
|
||||||
return this;
|
return this;
|
||||||
|
@ -216,5 +223,11 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
||||||
this.readyTimeoutMillis = readyTimeoutMillis;
|
this.readyTimeoutMillis = readyTimeoutMillis;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Whether to check server addresses against the SecurityPolicy *before* binding to them. */
|
||||||
|
public Builder setPreAuthorizeServers(boolean preAuthorizeServers) {
|
||||||
|
this.preAuthorizeServers = preAuthorizeServers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,8 +178,8 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder.
|
||||||
serverPolicyChecker,
|
serverPolicyChecker,
|
||||||
checkNotNull(executor, "Not started?"));
|
checkNotNull(executor, "Not started?"));
|
||||||
// Create a new transport and let our listener know about it.
|
// Create a new transport and let our listener know about it.
|
||||||
BinderTransport.BinderServerTransport transport =
|
BinderServerTransport transport =
|
||||||
new BinderTransport.BinderServerTransport(
|
new BinderServerTransport(
|
||||||
executorServicePool,
|
executorServicePool,
|
||||||
attrsBuilder.build(),
|
attrsBuilder.build(),
|
||||||
streamTracerFactories,
|
streamTracerFactories,
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
|
import io.grpc.Attributes;
|
||||||
|
import io.grpc.Grpc;
|
||||||
|
import io.grpc.Internal;
|
||||||
|
import io.grpc.InternalLogId;
|
||||||
|
import io.grpc.Metadata;
|
||||||
|
import io.grpc.ServerStreamTracer;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.internal.ObjectPool;
|
||||||
|
import io.grpc.internal.ServerStream;
|
||||||
|
import io.grpc.internal.ServerTransport;
|
||||||
|
import io.grpc.internal.ServerTransportListener;
|
||||||
|
import io.grpc.internal.StatsTraceContext;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Concrete server-side transport implementation. */
|
||||||
|
@Internal
|
||||||
|
public final class BinderServerTransport extends BinderTransport implements ServerTransport {
|
||||||
|
|
||||||
|
private final List<ServerStreamTracer.Factory> streamTracerFactories;
|
||||||
|
@Nullable private ServerTransportListener serverTransportListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new transport instance.
|
||||||
|
*
|
||||||
|
* @param binderDecorator used to decorate 'callbackBinder', for fault injection.
|
||||||
|
*/
|
||||||
|
public BinderServerTransport(
|
||||||
|
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||||
|
Attributes attributes,
|
||||||
|
List<ServerStreamTracer.Factory> streamTracerFactories,
|
||||||
|
OneWayBinderProxy.Decorator binderDecorator,
|
||||||
|
IBinder callbackBinder) {
|
||||||
|
super(executorServicePool, attributes, binderDecorator, buildLogId(attributes));
|
||||||
|
this.streamTracerFactories = streamTracerFactories;
|
||||||
|
// TODO(jdcormie): Plumb in the Server's executor() and use it here instead.
|
||||||
|
setOutgoingBinder(OneWayBinderProxy.wrap(callbackBinder, getScheduledExecutorService()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setServerTransportListener(
|
||||||
|
ServerTransportListener serverTransportListener) {
|
||||||
|
this.serverTransportListener = serverTransportListener;
|
||||||
|
if (isShutdown()) {
|
||||||
|
setState(TransportState.SHUTDOWN_TERMINATED);
|
||||||
|
notifyTerminated();
|
||||||
|
releaseExecutors();
|
||||||
|
} else {
|
||||||
|
sendSetupTransaction();
|
||||||
|
// Check we're not shutdown again, since a failure inside sendSetupTransaction (or a callback
|
||||||
|
// it triggers), could have shut us down.
|
||||||
|
if (!isShutdown()) {
|
||||||
|
setState(TransportState.READY);
|
||||||
|
attributes = serverTransportListener.transportReady(attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatsTraceContext createStatsTraceContext(String methodName, Metadata headers) {
|
||||||
|
return StatsTraceContext.newServerContext(streamTracerFactories, methodName, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized Status startStream(ServerStream stream, String methodName, Metadata headers) {
|
||||||
|
if (isShutdown()) {
|
||||||
|
return Status.UNAVAILABLE.withDescription("transport is shutdown");
|
||||||
|
} else {
|
||||||
|
serverTransportListener.streamCreated(stream, methodName, headers);
|
||||||
|
return Status.OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GuardedBy("this")
|
||||||
|
void notifyShutdown(Status status) {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GuardedBy("this")
|
||||||
|
void notifyTerminated() {
|
||||||
|
if (serverTransportListener != null) {
|
||||||
|
serverTransportListener.transportTerminated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void shutdown() {
|
||||||
|
shutdownInternal(Status.OK, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void shutdownNow(Status reason) {
|
||||||
|
shutdownInternal(reason, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
@GuardedBy("this")
|
||||||
|
protected Inbound<?> createInbound(int callId) {
|
||||||
|
return new Inbound.ServerInbound(this, attributes, callId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InternalLogId buildLogId(Attributes attributes) {
|
||||||
|
return InternalLogId.allocate(
|
||||||
|
BinderServerTransport.class, "from " + attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,65 +19,32 @@ package io.grpc.binder.internal;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.DeadObjectException;
|
import android.os.DeadObjectException;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Process;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.TransactionTooLargeException;
|
import android.os.TransactionTooLargeException;
|
||||||
|
import androidx.annotation.BinderThread;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Ticker;
|
|
||||||
import com.google.common.base.Verify;
|
import com.google.common.base.Verify;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.errorprone.annotations.CheckReturnValue;
|
|
||||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.CallOptions;
|
|
||||||
import io.grpc.ClientStreamTracer;
|
|
||||||
import io.grpc.Grpc;
|
|
||||||
import io.grpc.Internal;
|
import io.grpc.Internal;
|
||||||
import io.grpc.InternalChannelz.SocketStats;
|
import io.grpc.InternalChannelz.SocketStats;
|
||||||
import io.grpc.InternalLogId;
|
import io.grpc.InternalLogId;
|
||||||
import io.grpc.Metadata;
|
|
||||||
import io.grpc.MethodDescriptor;
|
|
||||||
import io.grpc.SecurityLevel;
|
|
||||||
import io.grpc.ServerStreamTracer;
|
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.StatusException;
|
import io.grpc.StatusException;
|
||||||
import io.grpc.binder.AndroidComponentAddress;
|
|
||||||
import io.grpc.binder.AsyncSecurityPolicy;
|
|
||||||
import io.grpc.binder.InboundParcelablePolicy;
|
import io.grpc.binder.InboundParcelablePolicy;
|
||||||
import io.grpc.binder.SecurityPolicy;
|
|
||||||
import io.grpc.internal.ClientStream;
|
|
||||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
|
||||||
import io.grpc.internal.ConnectionClientTransport;
|
|
||||||
import io.grpc.internal.FailingClientStream;
|
|
||||||
import io.grpc.internal.GrpcAttributes;
|
|
||||||
import io.grpc.internal.GrpcUtil;
|
|
||||||
import io.grpc.internal.ManagedClientTransport;
|
|
||||||
import io.grpc.internal.ObjectPool;
|
import io.grpc.internal.ObjectPool;
|
||||||
import io.grpc.internal.ServerStream;
|
|
||||||
import io.grpc.internal.ServerTransport;
|
|
||||||
import io.grpc.internal.ServerTransportListener;
|
|
||||||
import io.grpc.internal.StatsTraceContext;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -105,8 +72,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||||
* https://github.com/grpc/proposal/blob/master/L73-java-binderchannel/wireformat.md
|
* https://github.com/grpc/proposal/blob/master/L73-java-binderchannel/wireformat.md
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
public abstract class BinderTransport
|
public abstract class BinderTransport implements IBinder.DeathRecipient {
|
||||||
implements LeakSafeOneWayBinder.TransactionHandler, IBinder.DeathRecipient {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(BinderTransport.class.getName());
|
private static final Logger logger = Logger.getLogger(BinderTransport.class.getName());
|
||||||
|
|
||||||
|
@ -168,10 +134,10 @@ public abstract class BinderTransport
|
||||||
private static final int RESERVED_TRANSACTIONS = 1000;
|
private static final int RESERVED_TRANSACTIONS = 1000;
|
||||||
|
|
||||||
/** The first call ID we can use. */
|
/** The first call ID we can use. */
|
||||||
private static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
|
static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
|
||||||
|
|
||||||
/** The last call ID we can use. */
|
/** The last call ID we can use. */
|
||||||
private static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
|
static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
|
||||||
|
|
||||||
/** The states of this transport. */
|
/** The states of this transport. */
|
||||||
protected enum TransportState {
|
protected enum TransportState {
|
||||||
|
@ -210,12 +176,14 @@ public abstract class BinderTransport
|
||||||
private final FlowController flowController;
|
private final FlowController flowController;
|
||||||
|
|
||||||
/** The number of incoming bytes we've received. */
|
/** The number of incoming bytes we've received. */
|
||||||
private final AtomicLong numIncomingBytes;
|
// Only read/written on @BinderThread.
|
||||||
|
private long numIncomingBytes;
|
||||||
|
|
||||||
/** The number of incoming bytes we've told our peer we've received. */
|
/** The number of incoming bytes we've told our peer we've received. */
|
||||||
|
// Only read/written on @BinderThread.
|
||||||
private long acknowledgedIncomingBytes;
|
private long acknowledgedIncomingBytes;
|
||||||
|
|
||||||
private BinderTransport(
|
protected BinderTransport(
|
||||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||||
Attributes attributes,
|
Attributes attributes,
|
||||||
OneWayBinderProxy.Decorator binderDecorator,
|
OneWayBinderProxy.Decorator binderDecorator,
|
||||||
|
@ -225,10 +193,9 @@ public abstract class BinderTransport
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
this.logId = logId;
|
this.logId = logId;
|
||||||
scheduledExecutorService = executorServicePool.getObject();
|
scheduledExecutorService = executorServicePool.getObject();
|
||||||
incomingBinder = new LeakSafeOneWayBinder(this);
|
incomingBinder = new LeakSafeOneWayBinder(this::handleTransaction);
|
||||||
ongoingCalls = new ConcurrentHashMap<>();
|
ongoingCalls = new ConcurrentHashMap<>();
|
||||||
flowController = new FlowController(TRANSACTION_BYTES_WINDOW);
|
flowController = new FlowController(TRANSACTION_BYTES_WINDOW);
|
||||||
numIncomingBytes = new AtomicLong();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override in child class.
|
// Override in child class.
|
||||||
|
@ -299,7 +266,10 @@ public abstract class BinderTransport
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void binderDied() {
|
public synchronized void binderDied() {
|
||||||
shutdownInternal(Status.UNAVAILABLE.withDescription("Peer process crashed, exited or was killed (binderDied)"), true);
|
shutdownInternal(
|
||||||
|
Status.UNAVAILABLE.withDescription(
|
||||||
|
"Peer process crashed, exited or was killed (binderDied)"),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
|
@ -423,8 +393,9 @@ public abstract class BinderTransport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@BinderThread
|
||||||
public final boolean handleTransaction(int code, Parcel parcel) {
|
@VisibleForTesting
|
||||||
|
final boolean handleTransaction(int code, Parcel parcel) {
|
||||||
try {
|
try {
|
||||||
return handleTransactionInternal(code, parcel);
|
return handleTransactionInternal(code, parcel);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
@ -440,6 +411,7 @@ public abstract class BinderTransport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
private boolean handleTransactionInternal(int code, Parcel parcel) {
|
private boolean handleTransactionInternal(int code, Parcel parcel) {
|
||||||
if (code < FIRST_CALL_ID) {
|
if (code < FIRST_CALL_ID) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
@ -483,11 +455,12 @@ public abstract class BinderTransport
|
||||||
if (inbound != null) {
|
if (inbound != null) {
|
||||||
inbound.handleTransaction(parcel);
|
inbound.handleTransaction(parcel);
|
||||||
}
|
}
|
||||||
long nib = numIncomingBytes.addAndGet(size);
|
numIncomingBytes += size;
|
||||||
if ((nib - acknowledgedIncomingBytes) > TRANSACTION_BYTES_WINDOW_FORCE_ACK) {
|
if ((numIncomingBytes - acknowledgedIncomingBytes) > TRANSACTION_BYTES_WINDOW_FORCE_ACK) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
sendAcknowledgeBytes(checkNotNull(outgoingBinder));
|
sendAcknowledgeBytes(checkNotNull(outgoingBinder), numIncomingBytes);
|
||||||
}
|
}
|
||||||
|
acknowledgedIncomingBytes = numIncomingBytes;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -519,10 +492,8 @@ public abstract class BinderTransport
|
||||||
protected void handlePingResponse(Parcel parcel) {}
|
protected void handlePingResponse(Parcel parcel) {}
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private void sendAcknowledgeBytes(OneWayBinderProxy iBinder) {
|
private void sendAcknowledgeBytes(OneWayBinderProxy iBinder, long n) {
|
||||||
// Send a transaction to acknowledge reception of incoming data.
|
// Send a transaction to acknowledge reception of incoming data.
|
||||||
long n = numIncomingBytes.get();
|
|
||||||
acknowledgedIncomingBytes = n;
|
|
||||||
try (ParcelHolder parcel = ParcelHolder.obtain()) {
|
try (ParcelHolder parcel = ParcelHolder.obtain()) {
|
||||||
parcel.get().writeLong(n);
|
parcel.get().writeLong(n);
|
||||||
iBinder.transact(ACKNOWLEDGE_BYTES, parcel);
|
iBinder.transact(ACKNOWLEDGE_BYTES, parcel);
|
||||||
|
@ -553,414 +524,6 @@ public abstract class BinderTransport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Concrete client-side transport implementation. */
|
|
||||||
@ThreadSafe
|
|
||||||
@Internal
|
|
||||||
public static final class BinderClientTransport extends BinderTransport
|
|
||||||
implements ConnectionClientTransport, Bindable.Observer {
|
|
||||||
|
|
||||||
private final ObjectPool<? extends Executor> offloadExecutorPool;
|
|
||||||
private final Executor offloadExecutor;
|
|
||||||
private final SecurityPolicy securityPolicy;
|
|
||||||
private final Bindable serviceBinding;
|
|
||||||
|
|
||||||
/** Number of ongoing calls which keep this transport "in-use". */
|
|
||||||
private final AtomicInteger numInUseStreams;
|
|
||||||
|
|
||||||
private final long readyTimeoutMillis;
|
|
||||||
private final PingTracker pingTracker;
|
|
||||||
|
|
||||||
@Nullable private ManagedClientTransport.Listener clientTransportListener;
|
|
||||||
|
|
||||||
@GuardedBy("this")
|
|
||||||
private int latestCallId = FIRST_CALL_ID;
|
|
||||||
|
|
||||||
@GuardedBy("this")
|
|
||||||
private ScheduledFuture<?> readyTimeoutFuture; // != null iff timeout scheduled.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new transport instance.
|
|
||||||
*
|
|
||||||
* @param factory parameters common to all a Channel's transports
|
|
||||||
* @param targetAddress the fully resolved and load-balanced server address
|
|
||||||
* @param options other parameters that can vary as transports come and go within a Channel
|
|
||||||
*/
|
|
||||||
public BinderClientTransport(
|
|
||||||
BinderClientTransportFactory factory,
|
|
||||||
AndroidComponentAddress targetAddress,
|
|
||||||
ClientTransportOptions options) {
|
|
||||||
super(
|
|
||||||
factory.scheduledExecutorPool,
|
|
||||||
buildClientAttributes(
|
|
||||||
options.getEagAttributes(),
|
|
||||||
factory.sourceContext,
|
|
||||||
targetAddress,
|
|
||||||
factory.inboundParcelablePolicy),
|
|
||||||
factory.binderDecorator,
|
|
||||||
buildLogId(factory.sourceContext, targetAddress));
|
|
||||||
this.offloadExecutorPool = factory.offloadExecutorPool;
|
|
||||||
this.securityPolicy = factory.securityPolicy;
|
|
||||||
this.offloadExecutor = offloadExecutorPool.getObject();
|
|
||||||
this.readyTimeoutMillis = factory.readyTimeoutMillis;
|
|
||||||
numInUseStreams = new AtomicInteger();
|
|
||||||
pingTracker = new PingTracker(Ticker.systemTicker(), (id) -> sendPing(id));
|
|
||||||
|
|
||||||
serviceBinding =
|
|
||||||
new ServiceBinding(
|
|
||||||
factory.mainThreadExecutor,
|
|
||||||
factory.sourceContext,
|
|
||||||
factory.channelCredentials,
|
|
||||||
targetAddress.asBindIntent(),
|
|
||||||
targetAddress.getTargetUser() != null
|
|
||||||
? targetAddress.getTargetUser()
|
|
||||||
: factory.defaultTargetUserHandle,
|
|
||||||
factory.bindServiceFlags.toInteger(),
|
|
||||||
this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void releaseExecutors() {
|
|
||||||
super.releaseExecutors();
|
|
||||||
offloadExecutorPool.returnObject(offloadExecutor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onBound(IBinder binder) {
|
|
||||||
sendSetupTransaction(
|
|
||||||
binderDecorator.decorate(OneWayBinderProxy.wrap(binder, offloadExecutor)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onUnbound(Status reason) {
|
|
||||||
shutdownInternal(reason, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue
|
|
||||||
@Override
|
|
||||||
public synchronized Runnable start(ManagedClientTransport.Listener clientTransportListener) {
|
|
||||||
this.clientTransportListener = checkNotNull(clientTransportListener);
|
|
||||||
return () -> {
|
|
||||||
synchronized (BinderClientTransport.this) {
|
|
||||||
if (inState(TransportState.NOT_STARTED)) {
|
|
||||||
setState(TransportState.SETUP);
|
|
||||||
serviceBinding.bind();
|
|
||||||
if (readyTimeoutMillis >= 0) {
|
|
||||||
readyTimeoutFuture =
|
|
||||||
getScheduledExecutorService()
|
|
||||||
.schedule(
|
|
||||||
BinderClientTransport.this::onReadyTimeout,
|
|
||||||
readyTimeoutMillis,
|
|
||||||
MILLISECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void onReadyTimeout() {
|
|
||||||
if (inState(TransportState.SETUP)) {
|
|
||||||
readyTimeoutFuture = null;
|
|
||||||
shutdownInternal(
|
|
||||||
Status.DEADLINE_EXCEEDED.withDescription(
|
|
||||||
"Connect timeout " + readyTimeoutMillis + "ms lapsed"),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized ClientStream newStream(
|
|
||||||
final MethodDescriptor<?, ?> method,
|
|
||||||
final Metadata headers,
|
|
||||||
final CallOptions callOptions,
|
|
||||||
ClientStreamTracer[] tracers) {
|
|
||||||
if (!inState(TransportState.READY)) {
|
|
||||||
return newFailingClientStream(
|
|
||||||
isShutdown()
|
|
||||||
? shutdownStatus
|
|
||||||
: Status.INTERNAL.withDescription("newStream() before transportReady()"),
|
|
||||||
attributes,
|
|
||||||
headers,
|
|
||||||
tracers);
|
|
||||||
}
|
|
||||||
|
|
||||||
int callId = latestCallId++;
|
|
||||||
if (latestCallId == LAST_CALL_ID) {
|
|
||||||
latestCallId = FIRST_CALL_ID;
|
|
||||||
}
|
|
||||||
StatsTraceContext statsTraceContext =
|
|
||||||
StatsTraceContext.newClientContext(tracers, attributes, headers);
|
|
||||||
Inbound.ClientInbound inbound =
|
|
||||||
new Inbound.ClientInbound(
|
|
||||||
this, attributes, callId, GrpcUtil.shouldBeCountedForInUse(callOptions));
|
|
||||||
if (ongoingCalls.putIfAbsent(callId, inbound) != null) {
|
|
||||||
Status failure = Status.INTERNAL.withDescription("Clashing call IDs");
|
|
||||||
shutdownInternal(failure, true);
|
|
||||||
return newFailingClientStream(failure, attributes, headers, tracers);
|
|
||||||
} else {
|
|
||||||
if (inbound.countsForInUse() && numInUseStreams.getAndIncrement() == 0) {
|
|
||||||
clientTransportListener.transportInUse(true);
|
|
||||||
}
|
|
||||||
Outbound.ClientOutbound outbound =
|
|
||||||
new Outbound.ClientOutbound(this, callId, method, headers, statsTraceContext);
|
|
||||||
if (method.getType().clientSendsOneMessage()) {
|
|
||||||
return new SingleMessageClientStream(inbound, outbound, attributes);
|
|
||||||
} else {
|
|
||||||
return new MultiMessageClientStream(inbound, outbound, attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void unregisterInbound(Inbound<?> inbound) {
|
|
||||||
if (inbound.countsForInUse() && numInUseStreams.decrementAndGet() == 0) {
|
|
||||||
clientTransportListener.transportInUse(false);
|
|
||||||
}
|
|
||||||
super.unregisterInbound(inbound);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void ping(final PingCallback callback, Executor executor) {
|
|
||||||
pingTracker.startPing(callback, executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void shutdown(Status reason) {
|
|
||||||
checkNotNull(reason, "reason");
|
|
||||||
shutdownInternal(reason, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void shutdownNow(Status reason) {
|
|
||||||
checkNotNull(reason, "reason");
|
|
||||||
shutdownInternal(reason, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@GuardedBy("this")
|
|
||||||
void notifyShutdown(Status status) {
|
|
||||||
clientTransportListener.transportShutdown(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@GuardedBy("this")
|
|
||||||
void notifyTerminated() {
|
|
||||||
if (numInUseStreams.getAndSet(0) > 0) {
|
|
||||||
clientTransportListener.transportInUse(false);
|
|
||||||
}
|
|
||||||
if (readyTimeoutFuture != null) {
|
|
||||||
readyTimeoutFuture.cancel(false);
|
|
||||||
readyTimeoutFuture = null;
|
|
||||||
}
|
|
||||||
serviceBinding.unbind();
|
|
||||||
clientTransportListener.transportTerminated();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@GuardedBy("this")
|
|
||||||
protected void handleSetupTransport(Parcel parcel) {
|
|
||||||
int remoteUid = Binder.getCallingUid();
|
|
||||||
attributes = setSecurityAttrs(attributes, remoteUid);
|
|
||||||
if (inState(TransportState.SETUP)) {
|
|
||||||
int version = parcel.readInt();
|
|
||||||
IBinder binder = parcel.readStrongBinder();
|
|
||||||
if (version != WIRE_FORMAT_VERSION) {
|
|
||||||
shutdownInternal(
|
|
||||||
Status.UNAVAILABLE.withDescription("Wire format version mismatch"), true);
|
|
||||||
} else if (binder == null) {
|
|
||||||
shutdownInternal(
|
|
||||||
Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true);
|
|
||||||
} else {
|
|
||||||
ListenableFuture<Status> authFuture =
|
|
||||||
(securityPolicy instanceof AsyncSecurityPolicy)
|
|
||||||
? ((AsyncSecurityPolicy) securityPolicy).checkAuthorizationAsync(remoteUid)
|
|
||||||
: Futures.submit(
|
|
||||||
() -> securityPolicy.checkAuthorization(remoteUid), offloadExecutor);
|
|
||||||
Futures.addCallback(
|
|
||||||
authFuture,
|
|
||||||
new FutureCallback<Status>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Status result) {
|
|
||||||
handleAuthResult(binder, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable t) {
|
|
||||||
handleAuthResult(t);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
offloadExecutor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void handleAuthResult(IBinder binder, Status authorization) {
|
|
||||||
if (inState(TransportState.SETUP)) {
|
|
||||||
if (!authorization.isOk()) {
|
|
||||||
shutdownInternal(authorization, true);
|
|
||||||
} else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) {
|
|
||||||
shutdownInternal(
|
|
||||||
Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true);
|
|
||||||
} else {
|
|
||||||
// Check state again, since a failure inside setOutgoingBinder (or a callback it
|
|
||||||
// triggers), could have shut us down.
|
|
||||||
if (!isShutdown()) {
|
|
||||||
setState(TransportState.READY);
|
|
||||||
attributes = clientTransportListener.filterTransport(attributes);
|
|
||||||
clientTransportListener.transportReady();
|
|
||||||
if (readyTimeoutFuture != null) {
|
|
||||||
readyTimeoutFuture.cancel(false);
|
|
||||||
readyTimeoutFuture = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void handleAuthResult(Throwable t) {
|
|
||||||
shutdownInternal(
|
|
||||||
Status.INTERNAL.withDescription("Could not evaluate SecurityPolicy").withCause(t), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("this")
|
|
||||||
@Override
|
|
||||||
protected void handlePingResponse(Parcel parcel) {
|
|
||||||
pingTracker.onPingResponse(parcel.readInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ClientStream newFailingClientStream(
|
|
||||||
Status failure, Attributes attributes, Metadata headers, ClientStreamTracer[] tracers) {
|
|
||||||
StatsTraceContext statsTraceContext =
|
|
||||||
StatsTraceContext.newClientContext(tracers, attributes, headers);
|
|
||||||
statsTraceContext.clientOutboundHeaders();
|
|
||||||
return new FailingClientStream(failure, tracers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InternalLogId buildLogId(
|
|
||||||
Context sourceContext, AndroidComponentAddress targetAddress) {
|
|
||||||
return InternalLogId.allocate(
|
|
||||||
BinderClientTransport.class,
|
|
||||||
sourceContext.getClass().getSimpleName() + "->" + targetAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Attributes buildClientAttributes(
|
|
||||||
Attributes eagAttrs,
|
|
||||||
Context sourceContext,
|
|
||||||
AndroidComponentAddress targetAddress,
|
|
||||||
InboundParcelablePolicy inboundParcelablePolicy) {
|
|
||||||
return Attributes.newBuilder()
|
|
||||||
.set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) // Trust noone for now.
|
|
||||||
.set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs)
|
|
||||||
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, AndroidComponentAddress.forContext(sourceContext))
|
|
||||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, targetAddress)
|
|
||||||
.set(INBOUND_PARCELABLE_POLICY, inboundParcelablePolicy)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Attributes setSecurityAttrs(Attributes attributes, int uid) {
|
|
||||||
return attributes.toBuilder()
|
|
||||||
.set(REMOTE_UID, uid)
|
|
||||||
.set(
|
|
||||||
GrpcAttributes.ATTR_SECURITY_LEVEL,
|
|
||||||
uid == Process.myUid()
|
|
||||||
? SecurityLevel.PRIVACY_AND_INTEGRITY
|
|
||||||
: SecurityLevel.INTEGRITY) // TODO: Have the SecrityPolicy decide this.
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Concrete server-side transport implementation. */
|
|
||||||
@Internal
|
|
||||||
public static final class BinderServerTransport extends BinderTransport
|
|
||||||
implements ServerTransport {
|
|
||||||
|
|
||||||
private final List<ServerStreamTracer.Factory> streamTracerFactories;
|
|
||||||
@Nullable private ServerTransportListener serverTransportListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new transport instance.
|
|
||||||
*
|
|
||||||
* @param binderDecorator used to decorate 'callbackBinder', for fault injection.
|
|
||||||
*/
|
|
||||||
public BinderServerTransport(
|
|
||||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
|
||||||
Attributes attributes,
|
|
||||||
List<ServerStreamTracer.Factory> streamTracerFactories,
|
|
||||||
OneWayBinderProxy.Decorator binderDecorator,
|
|
||||||
IBinder callbackBinder) {
|
|
||||||
super(executorServicePool, attributes, binderDecorator, buildLogId(attributes));
|
|
||||||
this.streamTracerFactories = streamTracerFactories;
|
|
||||||
// TODO(jdcormie): Plumb in the Server's executor() and use it here instead.
|
|
||||||
setOutgoingBinder(OneWayBinderProxy.wrap(callbackBinder, getScheduledExecutorService()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void setServerTransportListener(
|
|
||||||
ServerTransportListener serverTransportListener) {
|
|
||||||
this.serverTransportListener = serverTransportListener;
|
|
||||||
if (isShutdown()) {
|
|
||||||
setState(TransportState.SHUTDOWN_TERMINATED);
|
|
||||||
notifyTerminated();
|
|
||||||
releaseExecutors();
|
|
||||||
} else {
|
|
||||||
sendSetupTransaction();
|
|
||||||
// Check we're not shutdown again, since a failure inside sendSetupTransaction (or a
|
|
||||||
// callback it triggers), could have shut us down.
|
|
||||||
if (!isShutdown()) {
|
|
||||||
setState(TransportState.READY);
|
|
||||||
attributes = serverTransportListener.transportReady(attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StatsTraceContext createStatsTraceContext(String methodName, Metadata headers) {
|
|
||||||
return StatsTraceContext.newServerContext(streamTracerFactories, methodName, headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized Status startStream(ServerStream stream, String methodName, Metadata headers) {
|
|
||||||
if (isShutdown()) {
|
|
||||||
return Status.UNAVAILABLE.withDescription("transport is shutdown");
|
|
||||||
} else {
|
|
||||||
serverTransportListener.streamCreated(stream, methodName, headers);
|
|
||||||
return Status.OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@GuardedBy("this")
|
|
||||||
void notifyShutdown(Status status) {
|
|
||||||
// Nothing to do.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@GuardedBy("this")
|
|
||||||
void notifyTerminated() {
|
|
||||||
if (serverTransportListener != null) {
|
|
||||||
serverTransportListener.transportTerminated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void shutdown() {
|
|
||||||
shutdownInternal(Status.OK, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void shutdownNow(Status reason) {
|
|
||||||
shutdownInternal(reason, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
@GuardedBy("this")
|
|
||||||
protected Inbound<?> createInbound(int callId) {
|
|
||||||
return new Inbound.ServerInbound(this, attributes, callId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InternalLogId buildLogId(Attributes attributes) {
|
|
||||||
return InternalLogId.allocate(
|
|
||||||
BinderServerTransport.class, "from " + attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkTransition(TransportState current, TransportState next) {
|
private static void checkTransition(TransportState current, TransportState next) {
|
||||||
switch (next) {
|
switch (next) {
|
||||||
case SETUP:
|
case SETUP:
|
||||||
|
|
|
@ -610,10 +610,9 @@ abstract class Inbound<L extends StreamListener> implements StreamListener.Messa
|
||||||
// Server-side inbound transactions.
|
// Server-side inbound transactions.
|
||||||
static final class ServerInbound extends Inbound<ServerStreamListener> {
|
static final class ServerInbound extends Inbound<ServerStreamListener> {
|
||||||
|
|
||||||
private final BinderTransport.BinderServerTransport serverTransport;
|
private final BinderServerTransport serverTransport;
|
||||||
|
|
||||||
ServerInbound(
|
ServerInbound(BinderServerTransport transport, Attributes attributes, int callId) {
|
||||||
BinderTransport.BinderServerTransport transport, Attributes attributes, int callId) {
|
|
||||||
super(transport, attributes, callId);
|
super(transport, attributes, callId);
|
||||||
this.serverTransport = transport;
|
this.serverTransport = transport;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,299 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static io.grpc.binder.internal.SystemApis.createContextAsUser;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import io.grpc.Attributes;
|
||||||
|
import io.grpc.EquivalentAddressGroup;
|
||||||
|
import io.grpc.NameResolver;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.StatusException;
|
||||||
|
import io.grpc.StatusOr;
|
||||||
|
import io.grpc.SynchronizationContext;
|
||||||
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import io.grpc.binder.ApiConstants;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link NameResolver} that resolves Android-standard "intent:" target URIs to the list of {@link
|
||||||
|
* AndroidComponentAddress} that match it by manifest intent filter.
|
||||||
|
*/
|
||||||
|
final class IntentNameResolver extends NameResolver {
|
||||||
|
private final Intent targetIntent; // Never mutated.
|
||||||
|
@Nullable private final UserHandle targetUser; // null means same user that hosts this process.
|
||||||
|
private final Context targetUserContext;
|
||||||
|
private final Executor offloadExecutor;
|
||||||
|
private final Executor sequentialExecutor;
|
||||||
|
private final SynchronizationContext syncContext;
|
||||||
|
private final ServiceConfigParser serviceConfigParser;
|
||||||
|
|
||||||
|
// Accessed only on `sequentialExecutor`
|
||||||
|
@Nullable private PackageChangeReceiver receiver; // != null when registered
|
||||||
|
|
||||||
|
// Accessed only on 'syncContext'.
|
||||||
|
private boolean shutdown;
|
||||||
|
private boolean queryNeeded;
|
||||||
|
@Nullable private Listener2 listener; // != null after start().
|
||||||
|
@Nullable private ListenableFuture<ResolutionResult> queryResultFuture; // != null when querying.
|
||||||
|
|
||||||
|
@EquivalentAddressGroup.Attr
|
||||||
|
private static final Attributes CONSTANT_EAG_ATTRS =
|
||||||
|
Attributes.newBuilder()
|
||||||
|
// Servers discovered in PackageManager are especially untrusted. After all, any app can
|
||||||
|
// declare any intent filter it wants! Require pre-authorization so that unauthorized apps
|
||||||
|
// don't even get a chance to run onCreate()/onBind().
|
||||||
|
.set(ApiConstants.PRE_AUTH_SERVER_OVERRIDE, true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
IntentNameResolver(Intent targetIntent, Args args) {
|
||||||
|
this.targetIntent = targetIntent;
|
||||||
|
this.targetUser = args.getArg(ApiConstants.TARGET_ANDROID_USER);
|
||||||
|
Context context =
|
||||||
|
checkNotNull(args.getArg(ApiConstants.SOURCE_ANDROID_CONTEXT), "SOURCE_ANDROID_CONTEXT")
|
||||||
|
.getApplicationContext();
|
||||||
|
this.targetUserContext =
|
||||||
|
targetUser != null ? createContextForTargetUserOrThrow(context, targetUser) : context;
|
||||||
|
// This Executor is nominally optional but all grpc-java Channels provide it since 1.25.
|
||||||
|
this.offloadExecutor =
|
||||||
|
checkNotNull(args.getOffloadExecutor(), "NameResolver.Args.getOffloadExecutor()");
|
||||||
|
// Ensures start()'s work runs before resolve()'s' work, and both run before shutdown()'s.
|
||||||
|
this.sequentialExecutor = MoreExecutors.newSequentialExecutor(offloadExecutor);
|
||||||
|
this.syncContext = args.getSynchronizationContext();
|
||||||
|
this.serviceConfigParser = args.getServiceConfigParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Context createContextForTargetUserOrThrow(Context context, UserHandle targetUser) {
|
||||||
|
try {
|
||||||
|
return createContextAsUser(context, targetUser, /* flags= */ 0); // @SystemApi since R.
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R and @SystemApi visibility");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Listener2 listener) {
|
||||||
|
checkState(this.listener == null, "Already started!");
|
||||||
|
checkState(!shutdown, "Resolver is shutdown");
|
||||||
|
this.listener = checkNotNull(listener);
|
||||||
|
sequentialExecutor.execute(this::registerReceiver);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
checkState(listener != null, "Not started!");
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolve() {
|
||||||
|
syncContext.throwIfNotInThisSynchronizationContext();
|
||||||
|
|
||||||
|
if (shutdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't block here in 'syncContext' so we offload PackageManager queries to an Executor.
|
||||||
|
// But offloading complicates things a bit because other calls can arrive while we wait for the
|
||||||
|
// results. We keep 'listener' up-to-date with the latest state in PackageManager by doing:
|
||||||
|
// 1. Only one query-and-report-to-listener operation at a time.
|
||||||
|
// 2. At least one query-and-report-to-listener AFTER every PackageManager state change.
|
||||||
|
if (queryResultFuture == null) {
|
||||||
|
queryResultFuture = Futures.submit(this::queryPackageManager, sequentialExecutor);
|
||||||
|
queryResultFuture.addListener(this::onQueryComplete, syncContext);
|
||||||
|
} else {
|
||||||
|
// There's already a query in-flight but (2) says we need at least one more. Our sequential
|
||||||
|
// Executor would be enough to ensure (1) but we also don't want a backlog of work to build up
|
||||||
|
// if things change rapidly. Just make a note to start a new query when this one finishes.
|
||||||
|
queryNeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onQueryComplete() {
|
||||||
|
syncContext.throwIfNotInThisSynchronizationContext();
|
||||||
|
checkState(queryResultFuture != null);
|
||||||
|
checkState(queryResultFuture.isDone());
|
||||||
|
|
||||||
|
// Capture non-final `listener` here while we're on 'syncContext'.
|
||||||
|
Listener2 listener = checkNotNull(this.listener);
|
||||||
|
Futures.addCallback(
|
||||||
|
queryResultFuture, // Already isDone() so this execute()s immediately.
|
||||||
|
new FutureCallback<ResolutionResult>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(ResolutionResult result) {
|
||||||
|
listener.onResult2(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
listener.onResult2(
|
||||||
|
ResolutionResult.newBuilder()
|
||||||
|
.setAddressesOrError(StatusOr.fromStatus(Status.fromThrowable(t)))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
syncContext); // Already on 'syncContext' but addCallback() is faster than try/get/catch.
|
||||||
|
queryResultFuture = null;
|
||||||
|
|
||||||
|
if (queryNeeded) {
|
||||||
|
// One or more resolve() requests arrived while we were working on the last one. Just one
|
||||||
|
// follow-on query can subsume all of them.
|
||||||
|
queryNeeded = false;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceAuthority() {
|
||||||
|
return "localhost";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
syncContext.throwIfNotInThisSynchronizationContext();
|
||||||
|
if (!shutdown) {
|
||||||
|
shutdown = true;
|
||||||
|
sequentialExecutor.execute(this::maybeUnregisterReceiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResolutionResult queryPackageManager() throws StatusException {
|
||||||
|
List<ResolveInfo> queryResults = queryIntentServices(targetIntent);
|
||||||
|
|
||||||
|
// Avoid a spurious UnsafeIntentLaunchViolation later. Since S, Android's StrictMode is very
|
||||||
|
// conservative, marking any Intent parsed from a string as suspicious and complaining when you
|
||||||
|
// bind to it. But all this is pointless with grpc-binder, which already goes even further by
|
||||||
|
// not trusting addresses at all! Instead, we rely on SecurityPolicy, which won't allow a
|
||||||
|
// connection to an unauthorized server UID no matter how you got there.
|
||||||
|
Intent prototypeBindIntent = sanitize(targetIntent);
|
||||||
|
|
||||||
|
// Model each matching android.app.Service as an EAG (server) with a single address.
|
||||||
|
List<EquivalentAddressGroup> addresses = new ArrayList<>();
|
||||||
|
for (ResolveInfo resolveInfo : queryResults) {
|
||||||
|
prototypeBindIntent.setComponent(
|
||||||
|
new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name));
|
||||||
|
addresses.add(
|
||||||
|
new EquivalentAddressGroup(
|
||||||
|
AndroidComponentAddress.newBuilder()
|
||||||
|
.setBindIntent(prototypeBindIntent) // Makes a copy.
|
||||||
|
.setTargetUser(targetUser)
|
||||||
|
.build(),
|
||||||
|
CONSTANT_EAG_ATTRS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResolutionResult.newBuilder()
|
||||||
|
.setAddressesOrError(StatusOr.fromValue(addresses))
|
||||||
|
// Empty service config means we get the default 'pick_first' load balancing policy.
|
||||||
|
.setServiceConfig(serviceConfigParser.parseServiceConfig(ImmutableMap.of()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ResolveInfo> queryIntentServices(Intent intent) throws StatusException {
|
||||||
|
int flags = 0;
|
||||||
|
if (Build.VERSION.SDK_INT >= 29) {
|
||||||
|
// Don't match direct-boot-unaware Services that can't presently be created. We'll query again
|
||||||
|
// after the user is unlocked. The MATCH_DIRECT_BOOT_AUTO behavior is actually the default but
|
||||||
|
// being explicit here avoids an android.os.strictmode.ImplicitDirectBootViolation.
|
||||||
|
flags |= PackageManager.MATCH_DIRECT_BOOT_AUTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ResolveInfo> intentServices =
|
||||||
|
targetUserContext.getPackageManager().queryIntentServices(intent, flags);
|
||||||
|
if (intentServices == null || intentServices.isEmpty()) {
|
||||||
|
// Must be the same as when ServiceBinding's call to bindService() returns false.
|
||||||
|
throw Status.UNIMPLEMENTED
|
||||||
|
.withDescription("Service not found for intent " + intent)
|
||||||
|
.asException();
|
||||||
|
}
|
||||||
|
return intentServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new Intent with the same action, data and categories as 'input'.
|
||||||
|
private static Intent sanitize(Intent input) {
|
||||||
|
Intent output = new Intent();
|
||||||
|
output.setAction(input.getAction());
|
||||||
|
output.setData(input.getData());
|
||||||
|
|
||||||
|
Set<String> categories = input.getCategories();
|
||||||
|
if (categories != null) {
|
||||||
|
for (String category : categories) {
|
||||||
|
output.addCategory(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't bother copying extras and flags since AndroidComponentAddress (rightly) ignores them.
|
||||||
|
// Don't bother copying package or ComponentName either, since we're about to set that.
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PackageChangeReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
// Get off the main thread and into the correct SynchronizationContext.
|
||||||
|
syncContext.executeLater(IntentNameResolver.this::resolve);
|
||||||
|
offloadExecutor.execute(syncContext::drain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnprotectedReceiver") // All of these are protected system broadcasts.
|
||||||
|
private void registerReceiver() {
|
||||||
|
checkState(receiver == null, "Already registered!");
|
||||||
|
receiver = new PackageChangeReceiver();
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addDataScheme("package");
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||||
|
|
||||||
|
targetUserContext.registerReceiver(receiver, filter);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 24) {
|
||||||
|
// Clients running in direct boot mode must refresh() when the user is unlocked because
|
||||||
|
// that's when `directBootAware=false` services become visible in queryIntentServices()
|
||||||
|
// results. ACTION_BOOT_COMPLETED would work too but it's delivered with lower priority.
|
||||||
|
targetUserContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeUnregisterReceiver() {
|
||||||
|
if (receiver != null) { // NameResolver API contract appears to allow shutdown without start().
|
||||||
|
targetUserContext.unregisterReceiver(receiver);
|
||||||
|
receiver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static android.content.Intent.URI_INTENT_SCHEME;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import io.grpc.NameResolver;
|
||||||
|
import io.grpc.NameResolver.Args;
|
||||||
|
import io.grpc.NameResolverProvider;
|
||||||
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link NameResolverProvider} that handles Android-standard "intent:" target URIs, resolving
|
||||||
|
* them to the list of {@link AndroidComponentAddress} that match by manifest intent filter.
|
||||||
|
*/
|
||||||
|
public final class IntentNameResolverProvider extends NameResolverProvider {
|
||||||
|
|
||||||
|
static final String ANDROID_INTENT_SCHEME = "intent";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultScheme() {
|
||||||
|
return ANDROID_INTENT_SCHEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public NameResolver newNameResolver(URI targetUri, final Args args) {
|
||||||
|
if (Objects.equals(targetUri.getScheme(), ANDROID_INTENT_SCHEME)) {
|
||||||
|
return new IntentNameResolver(parseUriArg(targetUri), args);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int priority() {
|
||||||
|
return 3; // Lower than DNS so we don't accidentally become the default scheme for a registry.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableSet<Class<? extends SocketAddress>> getProducedSocketAddressTypes() {
|
||||||
|
return ImmutableSet.of(AndroidComponentAddress.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent parseUriArg(URI targetUri) {
|
||||||
|
try {
|
||||||
|
return Intent.parseUri(targetUri.toString(), URI_INTENT_SCHEME);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package io.grpc.binder.internal;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
import androidx.annotation.BinderThread;
|
||||||
import io.grpc.Internal;
|
import io.grpc.Internal;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -58,6 +59,7 @@ public final class LeakSafeOneWayBinder extends Binder {
|
||||||
* @return the value to return from {@link Binder#onTransact}. NB: "oneway" semantics mean this
|
* @return the value to return from {@link Binder#onTransact}. NB: "oneway" semantics mean this
|
||||||
* result will not delivered to the caller of {@link IBinder#transact}
|
* result will not delivered to the caller of {@link IBinder#transact}
|
||||||
*/
|
*/
|
||||||
|
@BinderThread
|
||||||
boolean handleTransaction(int code, Parcel data);
|
boolean handleTransaction(int code, Parcel data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ package io.grpc.binder.internal;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
|
import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
|
||||||
import static java.lang.Math.max;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
|
@ -397,8 +396,7 @@ abstract class Outbound {
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
void setDeadline(Deadline deadline) {
|
void setDeadline(Deadline deadline) {
|
||||||
headers.discardAll(TIMEOUT_KEY);
|
headers.discardAll(TIMEOUT_KEY);
|
||||||
long effectiveTimeoutNanos = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
headers.put(TIMEOUT_KEY, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||||
headers.put(TIMEOUT_KEY, effectiveTimeoutNanos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,22 @@ import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import androidx.annotation.AnyThread;
|
import androidx.annotation.AnyThread;
|
||||||
import androidx.annotation.MainThread;
|
import androidx.annotation.MainThread;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.VerifyException;
|
||||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
|
import io.grpc.StatusException;
|
||||||
import io.grpc.binder.BinderChannelCredentials;
|
import io.grpc.binder.BinderChannelCredentials;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -85,6 +93,8 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
||||||
private final Observer observer;
|
private final Observer observer;
|
||||||
private final Executor mainThreadExecutor;
|
private final Executor mainThreadExecutor;
|
||||||
|
|
||||||
|
private static volatile Method queryIntentServicesAsUserMethod;
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private State state;
|
private State state;
|
||||||
|
|
||||||
|
@ -183,18 +193,27 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
||||||
bindResult = context.bindService(bindIntent, conn, flags);
|
bindResult = context.bindService(bindIntent, conn, flags);
|
||||||
break;
|
break;
|
||||||
case BIND_SERVICE_AS_USER:
|
case BIND_SERVICE_AS_USER:
|
||||||
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
|
||||||
|
} else {
|
||||||
|
return Status.INTERNAL.withDescription("Cross user Channel requires Android R+");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DEVICE_POLICY_BIND_SEVICE_ADMIN:
|
case DEVICE_POLICY_BIND_SEVICE_ADMIN:
|
||||||
DevicePolicyManager devicePolicyManager =
|
DevicePolicyManager devicePolicyManager =
|
||||||
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||||
bindResult =
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
bindResult =
|
||||||
channelCredentials.getDevicePolicyAdminComponentName(),
|
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
||||||
bindIntent,
|
channelCredentials.getDevicePolicyAdminComponentName(),
|
||||||
conn,
|
bindIntent,
|
||||||
flags,
|
conn,
|
||||||
targetUserHandle);
|
flags,
|
||||||
|
targetUserHandle);
|
||||||
|
} else {
|
||||||
|
return Status.INTERNAL.withDescription(
|
||||||
|
"Device policy admin binding requires Android R+");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!bindResult) {
|
if (!bindResult) {
|
||||||
|
@ -247,6 +266,71 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sadly the PackageManager#resolveServiceAsUser() API we need isn't part of the SDK or even a
|
||||||
|
// @SystemApi as of this writing. Modern Android prevents even system apps from calling it, by any
|
||||||
|
// means (https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces).
|
||||||
|
// So instead we call queryIntentServicesAsUser(), which does more than we need but *is* a
|
||||||
|
// @SystemApi in all the SDK versions where we support cross-user Channels.
|
||||||
|
@Nullable
|
||||||
|
private static ResolveInfo resolveServiceAsUser(
|
||||||
|
PackageManager packageManager, Intent intent, int flags, UserHandle targetUserHandle) {
|
||||||
|
List<ResolveInfo> results =
|
||||||
|
queryIntentServicesAsUser(packageManager, intent, flags, targetUserHandle);
|
||||||
|
// The first query result is "what would be returned by resolveService", per the javadoc.
|
||||||
|
return (results != null && !results.isEmpty()) ? results.get(0) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The cross-user Channel feature requires the client to be a system app so we assume @SystemApi
|
||||||
|
// queryIntentServicesAsUser() is visible to us at runtime. It would be visible at build time too,
|
||||||
|
// if our host system app were written to call it directly. We only have to use reflection here
|
||||||
|
// because grpc-java is a library built outside the Android source tree where the compiler can't
|
||||||
|
// see the "non-SDK" @SystemApis that we need.
|
||||||
|
@Nullable
|
||||||
|
@SuppressWarnings("unchecked") // Safe by PackageManager#queryIntentServicesAsUser spec in AOSP.
|
||||||
|
private static List<ResolveInfo> queryIntentServicesAsUser(
|
||||||
|
PackageManager packageManager, Intent intent, int flags, UserHandle targetUserHandle) {
|
||||||
|
try {
|
||||||
|
if (queryIntentServicesAsUserMethod == null) {
|
||||||
|
synchronized (ServiceBinding.class) {
|
||||||
|
if (queryIntentServicesAsUserMethod == null) {
|
||||||
|
queryIntentServicesAsUserMethod =
|
||||||
|
PackageManager.class.getMethod(
|
||||||
|
"queryIntentServicesAsUser", Intent.class, int.class, UserHandle.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (List<ResolveInfo>)
|
||||||
|
queryIntentServicesAsUserMethod.invoke(packageManager, intent, flags, targetUserHandle);
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new VerifyException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
@Override
|
||||||
|
public ServiceInfo resolve() throws StatusException {
|
||||||
|
checkState(sourceContext != null);
|
||||||
|
PackageManager packageManager = sourceContext.getPackageManager();
|
||||||
|
int flags = 0;
|
||||||
|
if (Build.VERSION.SDK_INT >= 29) {
|
||||||
|
// Filter out non-'directBootAware' <service>s when 'targetUserHandle' is locked. Here's why:
|
||||||
|
// Callers want 'bindIntent' to #resolve() to the same thing a follow-up call to #bind() will.
|
||||||
|
// But bindService() *always* ignores services that can't presently be created for lack of
|
||||||
|
// 'directBootAware'-ness. This flag explicitly tells resolveService() to act the same way.
|
||||||
|
flags |= PackageManager.MATCH_DIRECT_BOOT_AUTO;
|
||||||
|
}
|
||||||
|
ResolveInfo resolveInfo =
|
||||||
|
targetUserHandle != null
|
||||||
|
? resolveServiceAsUser(packageManager, bindIntent, flags, targetUserHandle)
|
||||||
|
: packageManager.resolveService(bindIntent, flags);
|
||||||
|
if (resolveInfo == null) {
|
||||||
|
throw Status.UNIMPLEMENTED // Same status code as when bindService() returns false.
|
||||||
|
.withDescription("resolveService(" + bindIntent + " / " + targetUserHandle + ") was null")
|
||||||
|
.asException();
|
||||||
|
}
|
||||||
|
return resolveInfo.serviceInfo;
|
||||||
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
private void clearReferences() {
|
private void clearReferences() {
|
||||||
sourceContext = null;
|
sourceContext = null;
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of static methods that wrap hidden Android "System APIs."
|
||||||
|
*
|
||||||
|
* <p>grpc-java can't call Android methods marked @SystemApi directly, even though many of our users
|
||||||
|
* are "system apps" entitled to do so. Being a library built outside the Android source tree, these
|
||||||
|
* "non-SDK" elements simply don't exist from our compiler's perspective. Instead we resort to
|
||||||
|
* reflection but use the static wrappers found here to keep call sites readable and type safe.
|
||||||
|
*
|
||||||
|
* <p>Modern Android's JRE also limits the visibility of these methods at *runtime*. Only certain
|
||||||
|
* privileged apps installed on the system image app can call them, even using reflection, and this
|
||||||
|
* wrapper doesn't change that. Callers are responsible for ensuring that the host app actually has
|
||||||
|
* the ability to call @SystemApis and all methods throw {@link ReflectiveOperationException} as a
|
||||||
|
* reminder to do that. See
|
||||||
|
* https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces for more.
|
||||||
|
*/
|
||||||
|
final class SystemApis {
|
||||||
|
private static volatile Method createContextAsUserMethod;
|
||||||
|
|
||||||
|
// Not to be instantiated.
|
||||||
|
private SystemApis() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new Context object whose methods act as if they were running in the given user.
|
||||||
|
*
|
||||||
|
* @throws ReflectiveOperationException if SDK_INT < R or host app lacks @SystemApi visibility
|
||||||
|
*/
|
||||||
|
public static Context createContextAsUser(Context context, UserHandle userHandle, int flags)
|
||||||
|
throws ReflectiveOperationException {
|
||||||
|
if (createContextAsUserMethod == null) {
|
||||||
|
synchronized (SystemApis.class) {
|
||||||
|
if (createContextAsUserMethod == null) {
|
||||||
|
createContextAsUserMethod =
|
||||||
|
Context.class.getMethod("createContextAsUser", UserHandle.class, int.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (Context) createContextAsUserMethod.invoke(context, userHandle, flags);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,13 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.core.content.pm.ApplicationInfoBuilder;
|
||||||
|
import androidx.test.core.content.pm.PackageInfoBuilder;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
@ -46,11 +52,13 @@ import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||||
import org.robolectric.annotation.LooperMode;
|
import org.robolectric.annotation.LooperMode;
|
||||||
import org.robolectric.annotation.LooperMode.Mode;
|
import org.robolectric.annotation.LooperMode.Mode;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||||
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
||||||
public final class RobolectricBinderSecurityTest {
|
public final class RobolectricBinderSecurityTest {
|
||||||
|
|
||||||
|
@ -62,10 +70,33 @@ public final class RobolectricBinderSecurityTest {
|
||||||
private ManagedChannel channel;
|
private ManagedChannel channel;
|
||||||
private Server server;
|
private Server server;
|
||||||
|
|
||||||
|
@Parameter public boolean preAuthServersParam;
|
||||||
|
|
||||||
|
@Parameters(name = "preAuthServersParam={0}")
|
||||||
|
public static ImmutableList<Boolean> data() {
|
||||||
|
return ImmutableList.of(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
ApplicationInfo serverAppInfo =
|
||||||
|
ApplicationInfoBuilder.newBuilder().setPackageName(context.getPackageName()).build();
|
||||||
|
serverAppInfo.uid = android.os.Process.myUid();
|
||||||
|
PackageInfo serverPkgInfo =
|
||||||
|
PackageInfoBuilder.newBuilder()
|
||||||
|
.setPackageName(serverAppInfo.packageName)
|
||||||
|
.setApplicationInfo(serverAppInfo)
|
||||||
|
.build();
|
||||||
|
shadowOf(context.getPackageManager()).installPackage(serverPkgInfo);
|
||||||
|
|
||||||
|
ServiceInfo serviceInfo = new ServiceInfo();
|
||||||
|
serviceInfo.name = "SomeService";
|
||||||
|
serviceInfo.packageName = serverAppInfo.packageName;
|
||||||
|
serviceInfo.applicationInfo = serverAppInfo;
|
||||||
|
shadowOf(context.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||||
|
|
||||||
AndroidComponentAddress listenAddress =
|
AndroidComponentAddress listenAddress =
|
||||||
AndroidComponentAddress.forRemoteComponent(context.getPackageName(), "HostService");
|
AndroidComponentAddress.forRemoteComponent(serviceInfo.packageName, serviceInfo.name);
|
||||||
|
|
||||||
MethodDescriptor<Empty, Empty> methodDesc = getMethodDescriptor();
|
MethodDescriptor<Empty, Empty> methodDesc = getMethodDescriptor();
|
||||||
ServerCallHandler<Empty, Empty> callHandler =
|
ServerCallHandler<Empty, Empty> callHandler =
|
||||||
|
@ -110,6 +141,7 @@ public final class RobolectricBinderSecurityTest {
|
||||||
checkNotNull(binderReceiver.get()));
|
checkNotNull(binderReceiver.get()));
|
||||||
channel =
|
channel =
|
||||||
BinderChannelBuilder.forAddress(listenAddress, context)
|
BinderChannelBuilder.forAddress(listenAddress, context)
|
||||||
|
.preAuthorizeServers(preAuthServersParam)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -357,7 +357,7 @@ public final class SecurityPoliciesTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = 21)
|
@Config(sdk = Config.OLDEST_SDK)
|
||||||
public void testIsProfileOwner_succeedsForProfileOwner() throws Exception {
|
public void testIsProfileOwner_succeedsForProfileOwner() throws Exception {
|
||||||
PackageInfo info =
|
PackageInfo info =
|
||||||
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
||||||
|
@ -371,7 +371,7 @@ public final class SecurityPoliciesTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = 21)
|
@Config(sdk = Config.OLDEST_SDK)
|
||||||
public void testIsProfileOwner_failsForNotProfileOwner() throws Exception {
|
public void testIsProfileOwner_failsForNotProfileOwner() throws Exception {
|
||||||
PackageInfo info =
|
PackageInfo info =
|
||||||
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
||||||
|
@ -385,7 +385,7 @@ public final class SecurityPoliciesTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = 21)
|
@Config(sdk = Config.OLDEST_SDK)
|
||||||
public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception {
|
public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception {
|
||||||
policy = SecurityPolicies.isProfileOwner(appContext);
|
policy = SecurityPolicies.isProfileOwner(appContext);
|
||||||
|
|
||||||
|
@ -425,7 +425,7 @@ public final class SecurityPoliciesTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = 21)
|
@Config(sdk = Config.OLDEST_SDK)
|
||||||
public void testIsProfileOwnerOnOrgOwned_failsForNotProfileOwner() throws Exception {
|
public void testIsProfileOwnerOnOrgOwned_failsForNotProfileOwner() throws Exception {
|
||||||
PackageInfo info =
|
PackageInfo info =
|
||||||
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
||||||
|
@ -439,7 +439,7 @@ public final class SecurityPoliciesTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = 21)
|
@Config(sdk = Config.OLDEST_SDK)
|
||||||
public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception {
|
public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception {
|
||||||
policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
|
policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package io.grpc.binder.internal;
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
@ -29,11 +28,9 @@ import android.os.Looper;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.Metadata;
|
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.internal.FixedObjectPool;
|
import io.grpc.internal.FixedObjectPool;
|
||||||
import io.grpc.internal.ServerStream;
|
import io.grpc.internal.MockServerTransportListener;
|
||||||
import io.grpc.internal.ServerTransportListener;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -55,21 +52,22 @@ public final class BinderServerTransportTest {
|
||||||
@Rule public MockitoRule mocks = MockitoJUnit.rule();
|
@Rule public MockitoRule mocks = MockitoJUnit.rule();
|
||||||
|
|
||||||
private final ScheduledExecutorService executorService = new MainThreadScheduledExecutorService();
|
private final ScheduledExecutorService executorService = new MainThreadScheduledExecutorService();
|
||||||
private final TestTransportListener transportListener = new TestTransportListener();
|
private MockServerTransportListener transportListener;
|
||||||
|
|
||||||
@Mock IBinder mockBinder;
|
@Mock IBinder mockBinder;
|
||||||
|
|
||||||
BinderTransport.BinderServerTransport transport;
|
BinderServerTransport transport;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
transport =
|
transport =
|
||||||
new BinderTransport.BinderServerTransport(
|
new BinderServerTransport(
|
||||||
new FixedObjectPool<>(executorService),
|
new FixedObjectPool<>(executorService),
|
||||||
Attributes.EMPTY,
|
Attributes.EMPTY,
|
||||||
ImmutableList.of(),
|
ImmutableList.of(),
|
||||||
OneWayBinderProxy.IDENTITY_DECORATOR,
|
OneWayBinderProxy.IDENTITY_DECORATOR,
|
||||||
mockBinder);
|
mockBinder);
|
||||||
|
transportListener = new MockServerTransportListener(transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -82,34 +80,6 @@ public final class BinderServerTransportTest {
|
||||||
transport.shutdownNow(Status.UNKNOWN.withDescription("reasons"));
|
transport.shutdownNow(Status.UNKNOWN.withDescription("reasons"));
|
||||||
shadowOf(Looper.getMainLooper()).idle();
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
assertThat(transportListener.terminated).isTrue();
|
assertThat(transportListener.isTerminated()).isTrue();
|
||||||
}
|
|
||||||
|
|
||||||
private static final class TestTransportListener implements ServerTransportListener {
|
|
||||||
|
|
||||||
public boolean ready;
|
|
||||||
public boolean terminated;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a new stream was created by the remote client.
|
|
||||||
*
|
|
||||||
* @param stream the newly created stream.
|
|
||||||
* @param method the fully qualified method name being called on the server.
|
|
||||||
* @param headers containing metadata for the call.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void streamCreated(ServerStream stream, String method, Metadata headers) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Attributes transportReady(Attributes attributes) {
|
|
||||||
ready = true;
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void transportTerminated() {
|
|
||||||
checkState(!terminated, "Terminated twice");
|
|
||||||
terminated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static android.os.Looper.getMainLooper;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import io.grpc.NameResolver;
|
||||||
|
import io.grpc.NameResolver.ResolutionResult;
|
||||||
|
import io.grpc.NameResolver.ServiceConfigParser;
|
||||||
|
import io.grpc.NameResolverProvider;
|
||||||
|
import io.grpc.SynchronizationContext;
|
||||||
|
import io.grpc.binder.ApiConstants;
|
||||||
|
import java.net.URI;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoTestRule;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
|
/** A test for IntentNameResolverProvider. */
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public final class IntentNameResolverProviderTest {
|
||||||
|
|
||||||
|
private final Application appContext = ApplicationProvider.getApplicationContext();
|
||||||
|
private final SynchronizationContext syncContext = newSynchronizationContext();
|
||||||
|
private final NameResolver.Args args = newNameResolverArgs();
|
||||||
|
|
||||||
|
private NameResolverProvider provider;
|
||||||
|
|
||||||
|
@Rule public MockitoTestRule mockitoTestRule = MockitoJUnit.testRule(this);
|
||||||
|
@Mock public NameResolver.Listener2 mockListener;
|
||||||
|
@Captor public ArgumentCaptor<ResolutionResult> resultCaptor;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
provider = new IntentNameResolverProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProviderScheme_returnsIntentScheme() throws Exception {
|
||||||
|
assertThat(provider.getDefaultScheme())
|
||||||
|
.isEqualTo(IntentNameResolverProvider.ANDROID_INTENT_SCHEME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoResolverForUnknownScheme_returnsNull() throws Exception {
|
||||||
|
assertThat(provider.newNameResolver(new URI("random://uri"), args)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolutionWithBadUri_throwsIllegalArg() throws Exception {
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> provider.newNameResolver(new URI("intent:xxx#Intent;e.x=1;end;"), args));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolverForIntentScheme_returnsResolver() throws Exception {
|
||||||
|
URI uri = new URI("intent://authority/path#Intent;action=action;scheme=scheme;end");
|
||||||
|
NameResolver resolver = provider.newNameResolver(uri, args);
|
||||||
|
assertThat(resolver).isNotNull();
|
||||||
|
assertThat(resolver.getServiceAuthority()).isEqualTo("localhost");
|
||||||
|
syncContext.execute(() -> resolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(resultCaptor.getValue().getAddressesOrError()).isNotNull();
|
||||||
|
syncContext.execute(resolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new test-specific {@link NameResolver.Args} instance. */
|
||||||
|
private NameResolver.Args newNameResolverArgs() {
|
||||||
|
return NameResolver.Args.newBuilder()
|
||||||
|
.setDefaultPort(-1)
|
||||||
|
.setProxyDetector((target) -> null) // No proxies here.
|
||||||
|
.setSynchronizationContext(syncContext)
|
||||||
|
.setOffloadExecutor(ContextCompat.getMainExecutor(appContext))
|
||||||
|
.setServiceConfigParser(mock(ServiceConfigParser.class))
|
||||||
|
.setArg(ApiConstants.SOURCE_ANDROID_CONTEXT, appContext)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SynchronizationContext newSynchronizationContext() {
|
||||||
|
return new SynchronizationContext(
|
||||||
|
(thread, exception) -> {
|
||||||
|
throw new AssertionError(exception);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,531 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_REPLACED;
|
||||||
|
import static android.os.Looper.getMainLooper;
|
||||||
|
import static android.os.Process.myUserHandle;
|
||||||
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.os.UserManager;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import io.grpc.EquivalentAddressGroup;
|
||||||
|
import io.grpc.NameResolver;
|
||||||
|
import io.grpc.NameResolver.ResolutionResult;
|
||||||
|
import io.grpc.NameResolver.ServiceConfigParser;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.StatusOr;
|
||||||
|
import io.grpc.SynchronizationContext;
|
||||||
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import io.grpc.binder.ApiConstants;
|
||||||
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoTestRule;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
import org.robolectric.shadows.ShadowPackageManager;
|
||||||
|
|
||||||
|
/** A test for IntentNameResolverProvider. */
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public final class IntentNameResolverTest {
|
||||||
|
|
||||||
|
private static final ComponentName SOME_COMPONENT_NAME =
|
||||||
|
new ComponentName("com.foo.bar", "SomeComponent");
|
||||||
|
private static final ComponentName ANOTHER_COMPONENT_NAME =
|
||||||
|
new ComponentName("org.blah", "AnotherComponent");
|
||||||
|
private final Application appContext = ApplicationProvider.getApplicationContext();
|
||||||
|
private final SynchronizationContext syncContext = newSynchronizationContext();
|
||||||
|
private final NameResolver.Args args = newNameResolverArgs().build();
|
||||||
|
|
||||||
|
private final ShadowPackageManager shadowPackageManager =
|
||||||
|
shadowOf(appContext.getPackageManager());
|
||||||
|
|
||||||
|
@Rule public MockitoTestRule mockitoTestRule = MockitoJUnit.testRule(this);
|
||||||
|
@Mock public NameResolver.Listener2 mockListener;
|
||||||
|
@Captor public ArgumentCaptor<ResolutionResult> resultCaptor;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolverForIntentScheme_returnsResolverWithLocalHostAuthority() throws Exception {
|
||||||
|
NameResolver resolver = newNameResolver(newIntent());
|
||||||
|
assertThat(resolver).isNotNull();
|
||||||
|
assertThat(resolver.getServiceAuthority()).isEqualTo("localhost");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolutionWithoutServicesAvailable_returnsUnimplemented() throws Exception {
|
||||||
|
NameResolver nameResolver = newNameResolver(newIntent());
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(resultCaptor.getValue().getAddressesOrError().getStatus().getCode())
|
||||||
|
.isEqualTo(Status.UNIMPLEMENTED.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolutionWithMultipleServicesAvailable_returnsAndroidComponentAddresses()
|
||||||
|
throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
// Adds another valid Service
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
NameResolver nameResolver = newNameResolver(intent);
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExplicitResolutionByComponent_returnsRestrictedResults() throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
NameResolver nameResolver =
|
||||||
|
newNameResolver(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME));
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExplicitResolutionByPackage_returnsRestrictedResults() throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
NameResolver nameResolver =
|
||||||
|
newNameResolver(intent.cloneFilter().setPackage(ANOTHER_COMPONENT_NAME.getPackageName()));
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolution_setsPreAuthEagAttribute() throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
NameResolver nameResolver = newNameResolver(intent);
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||||
|
assertThat(
|
||||||
|
getEagsOrThrow(resultCaptor.getValue()).stream()
|
||||||
|
.map(EquivalentAddressGroup::getAttributes)
|
||||||
|
.collect(toImmutableList())
|
||||||
|
.get(0)
|
||||||
|
.get(ApiConstants.PRE_AUTH_SERVER_OVERRIDE))
|
||||||
|
.isTrue();
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServiceRemoved_pushesUpdatedAndroidComponentAddresses() throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
NameResolver nameResolver = newNameResolver(intent);
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
shadowPackageManager.removeService(ANOTHER_COMPONENT_NAME);
|
||||||
|
broadcastPackageChange(ACTION_PACKAGE_REPLACED, ANOTHER_COMPONENT_NAME.getPackageName());
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verifyNoMoreInteractions(mockListener);
|
||||||
|
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 30)
|
||||||
|
public void testTargetAndroidUser_pushesUpdatedAddresses() throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
NameResolver nameResolver =
|
||||||
|
newNameResolver(
|
||||||
|
intent,
|
||||||
|
newNameResolverArgs().setArg(ApiConstants.TARGET_ANDROID_USER, myUserHandle()).build());
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(resultCaptor.getValue().getAddressesOrError().getStatus().getCode())
|
||||||
|
.isEqualTo(Status.UNIMPLEMENTED.getCode());
|
||||||
|
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
broadcastPackageChange(ACTION_PACKAGE_ADDED, SOME_COMPONENT_NAME.getPackageName());
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(
|
||||||
|
ImmutableList.of(
|
||||||
|
AndroidComponentAddress.newBuilder()
|
||||||
|
.setTargetUser(myUserHandle())
|
||||||
|
.setBindIntent(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME))
|
||||||
|
.build()));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verifyNoMoreInteractions(mockListener);
|
||||||
|
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 29)
|
||||||
|
public void testTargetAndroidUser_notSupported_throwsWithHelpfulMessage() throws Exception {
|
||||||
|
NameResolver.Args args =
|
||||||
|
newNameResolverArgs().setArg(ApiConstants.TARGET_ANDROID_USER, myUserHandle()).build();
|
||||||
|
IllegalArgumentException iae =
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> newNameResolver(newIntent(), args));
|
||||||
|
assertThat(iae.getMessage()).contains("TARGET_ANDROID_USER");
|
||||||
|
assertThat(iae.getMessage()).contains("SDK_INT >= R");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 29)
|
||||||
|
public void testServiceAppearsUponBootComplete_pushesUpdatedAndroidComponentAddresses()
|
||||||
|
throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
// Suppose this directBootAware=true Service appears in PackageManager before a user unlock.
|
||||||
|
shadowOf(appContext.getSystemService(UserManager.class)).setUserUnlocked(false);
|
||||||
|
ServiceInfo someServiceInfo = shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
someServiceInfo.directBootAware = true;
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
NameResolver nameResolver = newNameResolver(intent);
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
// TODO(b/331618070): Robolectric doesn't yet support ServiceInfo.directBootAware filtering.
|
||||||
|
// Simulate support by waiting for a user unlock to add this !directBootAware Service.
|
||||||
|
ServiceInfo anotherServiceInfo =
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||||
|
anotherServiceInfo.directBootAware = false;
|
||||||
|
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
shadowOf(appContext.getSystemService(UserManager.class)).setUserUnlocked(true);
|
||||||
|
broadcastUserUnlocked(myUserHandle());
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verifyNoMoreInteractions(mockListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefresh_returnsSameAndroidComponentAddresses() throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
NameResolver nameResolver = newNameResolver(intent);
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::refresh);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||||
|
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefresh_collapsesMultipleRequestsIntoOneLookup() throws Exception {
|
||||||
|
Intent intent = newIntent();
|
||||||
|
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||||
|
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||||
|
|
||||||
|
NameResolver nameResolver = newNameResolver(intent);
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener)); // Should kick off the 1st lookup.
|
||||||
|
syncContext.execute(nameResolver::refresh); // Should queue a lookup to run when 1st finishes.
|
||||||
|
syncContext.execute(nameResolver::refresh); // Should be ignored since a lookup is already Q'd.
|
||||||
|
syncContext.execute(nameResolver::refresh); // Also ignored.
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||||
|
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||||
|
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||||
|
|
||||||
|
syncContext.execute(nameResolver::shutdown);
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastPackageChange(String action, String pkgName) {
|
||||||
|
Intent broadcast = new Intent();
|
||||||
|
broadcast.setAction(action);
|
||||||
|
broadcast.setData(Uri.parse("package:" + pkgName));
|
||||||
|
appContext.sendBroadcast(broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastUserUnlocked(UserHandle userHandle) {
|
||||||
|
Intent unlockedBroadcast = new Intent(Intent.ACTION_USER_UNLOCKED);
|
||||||
|
unlockedBroadcast.putExtra(Intent.EXTRA_USER, userHandle);
|
||||||
|
appContext.sendBroadcast(unlockedBroadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolutionOnResultThrows_onErrorNotCalled() throws Exception {
|
||||||
|
RetainingUncaughtExceptionHandler exceptionHandler = new RetainingUncaughtExceptionHandler();
|
||||||
|
SynchronizationContext syncContext = new SynchronizationContext(exceptionHandler);
|
||||||
|
Intent intent = newIntent();
|
||||||
|
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||||
|
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, newFilterMatching(intent));
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
class SomeRuntimeException extends RuntimeException {}
|
||||||
|
doThrow(SomeRuntimeException.class).when(mockListener).onResult2(any());
|
||||||
|
|
||||||
|
NameResolver nameResolver =
|
||||||
|
newNameResolver(
|
||||||
|
intent, newNameResolverArgs().setSynchronizationContext(syncContext).build());
|
||||||
|
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mockListener).onResult2(any());
|
||||||
|
verify(mockListener, never()).onError(any());
|
||||||
|
assertThat(exceptionHandler.uncaught).hasSize(1);
|
||||||
|
assertThat(exceptionHandler.uncaught.get(0)).isInstanceOf(SomeRuntimeException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent newIntent() {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction("test.action");
|
||||||
|
intent.setData(Uri.parse("grpc:ServiceName"));
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IntentFilter newFilterMatching(Intent intent) {
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
if (intent.getAction() != null) {
|
||||||
|
filter.addAction(intent.getAction());
|
||||||
|
}
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
if (data.getScheme() != null) {
|
||||||
|
filter.addDataScheme(data.getScheme());
|
||||||
|
}
|
||||||
|
if (data.getSchemeSpecificPart() != null) {
|
||||||
|
filter.addDataSchemeSpecificPart(data.getSchemeSpecificPart(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Set<String> categories = intent.getCategories();
|
||||||
|
if (categories != null) {
|
||||||
|
for (String category : categories) {
|
||||||
|
filter.addCategory(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<EquivalentAddressGroup> getEagsOrThrow(ResolutionResult result) {
|
||||||
|
StatusOr<List<EquivalentAddressGroup>> eags = result.getAddressesOrError();
|
||||||
|
if (!eags.hasValue()) {
|
||||||
|
throw eags.getStatus().asRuntimeException();
|
||||||
|
}
|
||||||
|
return eags.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracts just the addresses from 'result's EquivalentAddressGroups.
|
||||||
|
private static ImmutableList<List<SocketAddress>> getAddressesOrThrow(ResolutionResult result) {
|
||||||
|
return getEagsOrThrow(result).stream()
|
||||||
|
.map(EquivalentAddressGroup::getAddresses)
|
||||||
|
.collect(toImmutableList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts given Intents to a list of ACAs, for convenient comparison with getAddressesOrThrow().
|
||||||
|
private static ImmutableList<AndroidComponentAddress> toAddressList(Intent... bindIntents) {
|
||||||
|
ImmutableList.Builder<AndroidComponentAddress> builder = ImmutableList.builder();
|
||||||
|
for (Intent bindIntent : bindIntents) {
|
||||||
|
builder.add(AndroidComponentAddress.forBindIntent(bindIntent));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NameResolver newNameResolver(Intent targetIntent) {
|
||||||
|
return newNameResolver(targetIntent, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NameResolver newNameResolver(Intent targetIntent, NameResolver.Args args) {
|
||||||
|
return new IntentNameResolver(targetIntent, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new test-specific {@link NameResolver.Args} instance. */
|
||||||
|
private NameResolver.Args.Builder newNameResolverArgs() {
|
||||||
|
return NameResolver.Args.newBuilder()
|
||||||
|
.setDefaultPort(-1)
|
||||||
|
.setProxyDetector((target) -> null) // No proxies here.
|
||||||
|
.setSynchronizationContext(syncContext)
|
||||||
|
.setOffloadExecutor(ContextCompat.getMainExecutor(appContext))
|
||||||
|
.setArg(ApiConstants.SOURCE_ANDROID_CONTEXT, appContext)
|
||||||
|
.setServiceConfigParser(mock(ServiceConfigParser.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a test {@link SynchronizationContext}.
|
||||||
|
*
|
||||||
|
* <p>Exceptions will cause the test to fail with {@link AssertionError}.
|
||||||
|
*/
|
||||||
|
private static SynchronizationContext newSynchronizationContext() {
|
||||||
|
return new SynchronizationContext(
|
||||||
|
(thread, exception) -> {
|
||||||
|
throw new AssertionError(exception);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class RetainingUncaughtExceptionHandler implements UncaughtExceptionHandler {
|
||||||
|
final ArrayList<Throwable> uncaught = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
|
||||||
|
uncaught.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,13 +16,29 @@
|
||||||
|
|
||||||
package io.grpc.binder.internal;
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.timeout;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.core.content.pm.ApplicationInfoBuilder;
|
||||||
|
import androidx.test.core.content.pm.PackageInfoBuilder;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import io.grpc.Attributes;
|
||||||
import io.grpc.ServerStreamTracer;
|
import io.grpc.ServerStreamTracer;
|
||||||
|
import io.grpc.Status;
|
||||||
import io.grpc.binder.AndroidComponentAddress;
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import io.grpc.binder.ApiConstants;
|
||||||
|
import io.grpc.binder.AsyncSecurityPolicy;
|
||||||
|
import io.grpc.binder.internal.SettableAsyncSecurityPolicy.AuthRequest;
|
||||||
import io.grpc.internal.AbstractTransportTest;
|
import io.grpc.internal.AbstractTransportTest;
|
||||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||||
import io.grpc.internal.GrpcUtil;
|
import io.grpc.internal.GrpcUtil;
|
||||||
|
@ -33,12 +49,20 @@ import io.grpc.internal.SharedResourcePool;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||||
import org.robolectric.annotation.LooperMode;
|
import org.robolectric.annotation.LooperMode;
|
||||||
import org.robolectric.annotation.LooperMode.Mode;
|
import org.robolectric.annotation.LooperMode.Mode;
|
||||||
|
import org.robolectric.shadows.ShadowBinder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All of the AbstractTransportTest cases applied to {@link BinderTransport} running in a
|
* All of the AbstractTransportTest cases applied to {@link BinderTransport} running in a
|
||||||
|
@ -52,7 +76,7 @@ import org.robolectric.annotation.LooperMode.Mode;
|
||||||
* meaning test cases don't run on the main thread. This supports the AbstractTransportTest approach
|
* meaning test cases don't run on the main thread. This supports the AbstractTransportTest approach
|
||||||
* where the test thread frequently blocks waiting for transport state changes to take effect.
|
* where the test thread frequently blocks waiting for transport state changes to take effect.
|
||||||
*/
|
*/
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||||
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
||||||
public final class RobolectricBinderTransportTest extends AbstractTransportTest {
|
public final class RobolectricBinderTransportTest extends AbstractTransportTest {
|
||||||
|
|
||||||
|
@ -64,14 +88,57 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest
|
||||||
private final ObjectPool<Executor> serverExecutorPool =
|
private final ObjectPool<Executor> serverExecutorPool =
|
||||||
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
|
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
|
||||||
|
|
||||||
|
@Rule public MockitoRule mocks = MockitoJUnit.rule();
|
||||||
|
|
||||||
|
@Mock AsyncSecurityPolicy mockClientSecurityPolicy;
|
||||||
|
|
||||||
|
ApplicationInfo serverAppInfo;
|
||||||
|
PackageInfo serverPkgInfo;
|
||||||
|
ServiceInfo serviceInfo;
|
||||||
|
|
||||||
private int nextServerAddress;
|
private int nextServerAddress;
|
||||||
|
|
||||||
|
@Parameter public boolean preAuthServersParam;
|
||||||
|
|
||||||
|
@Parameters(name = "preAuthServersParam={0}")
|
||||||
|
public static ImmutableList<Boolean> data() {
|
||||||
|
return ImmutableList.of(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() {
|
||||||
|
serverAppInfo =
|
||||||
|
ApplicationInfoBuilder.newBuilder().setPackageName("the.server.package").build();
|
||||||
|
serverAppInfo.uid = android.os.Process.myUid();
|
||||||
|
serverPkgInfo =
|
||||||
|
PackageInfoBuilder.newBuilder()
|
||||||
|
.setPackageName(serverAppInfo.packageName)
|
||||||
|
.setApplicationInfo(serverAppInfo)
|
||||||
|
.build();
|
||||||
|
shadowOf(application.getPackageManager()).installPackage(serverPkgInfo);
|
||||||
|
|
||||||
|
serviceInfo = new ServiceInfo();
|
||||||
|
serviceInfo.name = "SomeService";
|
||||||
|
serviceInfo.packageName = serverAppInfo.packageName;
|
||||||
|
serviceInfo.applicationInfo = serverAppInfo;
|
||||||
|
shadowOf(application.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||||
|
|
||||||
|
super.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void requestRealisticBindServiceBehavior() {
|
||||||
|
shadowOf(application).setBindServiceCallsOnServiceConnectedDirectly(false);
|
||||||
|
shadowOf(application).setUnbindServiceCallsOnServiceDisconnected(false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
|
protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
|
||||||
AndroidComponentAddress listenAddr = AndroidComponentAddress.forBindIntent(
|
AndroidComponentAddress listenAddr =
|
||||||
new Intent()
|
AndroidComponentAddress.forBindIntent(
|
||||||
.setClassName(application.getPackageName(), "HostService")
|
new Intent()
|
||||||
.setAction("io.grpc.action.BIND." + nextServerAddress++));
|
.setClassName(serviceInfo.packageName, serviceInfo.name)
|
||||||
|
.setAction("io.grpc.action.BIND." + nextServerAddress++));
|
||||||
|
|
||||||
BinderServer binderServer =
|
BinderServer binderServer =
|
||||||
new BinderServer.Builder()
|
new BinderServer.Builder()
|
||||||
|
@ -81,6 +148,7 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest
|
||||||
.setStreamTracerFactories(streamTracerFactories)
|
.setStreamTracerFactories(streamTracerFactories)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
shadowOf(application.getPackageManager()).addServiceIfNotPresent(listenAddr.getComponent());
|
||||||
shadowOf(application)
|
shadowOf(application)
|
||||||
.setComponentNameAndServiceForBindServiceForIntent(
|
.setComponentNameAndServiceForBindServiceForIntent(
|
||||||
listenAddr.asBindIntent(), listenAddr.getComponent(), binderServer.getHostBinder());
|
listenAddr.asBindIntent(), listenAddr.getComponent(), binderServer.getHostBinder());
|
||||||
|
@ -97,22 +165,30 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest
|
||||||
return newServer(streamTracerFactories);
|
return newServer(streamTracerFactories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BinderClientTransportFactory.Builder newClientTransportFactoryBuilder() {
|
||||||
|
return new BinderClientTransportFactory.Builder()
|
||||||
|
.setPreAuthorizeServers(preAuthServersParam)
|
||||||
|
.setSourceContext(application)
|
||||||
|
.setScheduledExecutorPool(executorServicePool)
|
||||||
|
.setOffloadExecutorPool(offloadExecutorPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
BinderClientTransportBuilder newClientTransportBuilder() {
|
||||||
|
return new BinderClientTransportBuilder()
|
||||||
|
.setFactory(newClientTransportFactoryBuilder().buildClientTransportFactory())
|
||||||
|
.setServerAddress(server.getListenSocketAddress());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ManagedClientTransport newClientTransport(InternalServer server) {
|
protected ManagedClientTransport newClientTransport(InternalServer server) {
|
||||||
BinderClientTransportFactory.Builder builder =
|
|
||||||
new BinderClientTransportFactory.Builder()
|
|
||||||
.setSourceContext(application)
|
|
||||||
.setScheduledExecutorPool(executorServicePool)
|
|
||||||
.setOffloadExecutorPool(offloadExecutorPool);
|
|
||||||
|
|
||||||
ClientTransportOptions options = new ClientTransportOptions();
|
ClientTransportOptions options = new ClientTransportOptions();
|
||||||
options.setEagAttributes(eagAttrs());
|
options.setEagAttributes(eagAttrs());
|
||||||
options.setChannelLogger(transportLogger());
|
options.setChannelLogger(transportLogger());
|
||||||
|
|
||||||
return new BinderTransport.BinderClientTransport(
|
return newClientTransportBuilder()
|
||||||
builder.buildClientTransportFactory(),
|
.setServerAddress(server.getListenSocketAddress())
|
||||||
(AndroidComponentAddress) server.getListenSocketAddress(),
|
.setOptions(options)
|
||||||
options);
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -120,6 +196,74 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest
|
||||||
return ((AndroidComponentAddress) server.getListenSocketAddress()).getAuthority();
|
return ((AndroidComponentAddress) server.getListenSocketAddress()).getAuthority();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clientAuthorizesServerUidsInOrder() throws Exception {
|
||||||
|
// TODO(jdcormie): In real Android, Binder#getCallingUid is thread-local but Robolectric only
|
||||||
|
// lets us fake value this *globally*. So the ShadowBinder#setCallingUid() here unrealistically
|
||||||
|
// affects the server's view of the client's uid too. For now this doesn't matter because this
|
||||||
|
// test never exercises server SecurityPolicy.
|
||||||
|
ShadowBinder.setCallingUid(11111); // UID of the server *process*.
|
||||||
|
|
||||||
|
serverPkgInfo.applicationInfo.uid = 22222; // UID of the server *app*, which can be different.
|
||||||
|
shadowOf(application.getPackageManager()).installPackage(serverPkgInfo);
|
||||||
|
shadowOf(application.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||||
|
server = newServer(ImmutableList.of());
|
||||||
|
server.start(serverListener);
|
||||||
|
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
|
client =
|
||||||
|
newClientTransportBuilder()
|
||||||
|
.setFactory(
|
||||||
|
newClientTransportFactoryBuilder()
|
||||||
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.buildClientTransportFactory())
|
||||||
|
.build();
|
||||||
|
runIfNotNull(client.start(mockClientTransportListener));
|
||||||
|
|
||||||
|
if (preAuthServersParam) {
|
||||||
|
AuthRequest preAuthRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
assertThat(preAuthRequest.uid).isEqualTo(22222);
|
||||||
|
verify(mockClientTransportListener, never()).transportReady();
|
||||||
|
preAuthRequest.setResult(Status.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthRequest authRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
assertThat(authRequest.uid).isEqualTo(11111);
|
||||||
|
verify(mockClientTransportListener, never()).transportReady();
|
||||||
|
authRequest.setResult(Status.OK);
|
||||||
|
|
||||||
|
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void eagAttributeCanOverrideChannelPreAuthServerSetting() throws Exception {
|
||||||
|
server.start(serverListener);
|
||||||
|
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||||
|
ClientTransportOptions options = new ClientTransportOptions();
|
||||||
|
options.setEagAttributes(
|
||||||
|
Attributes.newBuilder().set(ApiConstants.PRE_AUTH_SERVER_OVERRIDE, true).build());
|
||||||
|
client =
|
||||||
|
newClientTransportBuilder()
|
||||||
|
.setOptions(options)
|
||||||
|
.setFactory(
|
||||||
|
newClientTransportFactoryBuilder()
|
||||||
|
.setPreAuthorizeServers(preAuthServersParam) // To be overridden.
|
||||||
|
.setSecurityPolicy(securityPolicy)
|
||||||
|
.buildClientTransportFactory())
|
||||||
|
.build();
|
||||||
|
runIfNotNull(client.start(mockClientTransportListener));
|
||||||
|
|
||||||
|
AuthRequest preAuthRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
verify(mockClientTransportListener, never()).transportReady();
|
||||||
|
preAuthRequest.setResult(Status.OK);
|
||||||
|
|
||||||
|
AuthRequest authRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
verify(mockClientTransportListener, never()).transportReady();
|
||||||
|
authRequest.setResult(Status.OK);
|
||||||
|
|
||||||
|
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportReady();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("See BinderTransportTest#socketStats.")
|
@Ignore("See BinderTransportTest#socketStats.")
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,6 +19,7 @@ package io.grpc.binder.internal;
|
||||||
import static android.content.Context.BIND_AUTO_CREATE;
|
import static android.content.Context.BIND_AUTO_CREATE;
|
||||||
import static android.os.Looper.getMainLooper;
|
import static android.os.Looper.getMainLooper;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
@ -27,6 +28,8 @@ import android.app.admin.DevicePolicyManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
|
@ -34,6 +37,7 @@ import androidx.core.content.ContextCompat;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.Status.Code;
|
import io.grpc.Status.Code;
|
||||||
|
import io.grpc.StatusException;
|
||||||
import io.grpc.binder.BinderChannelCredentials;
|
import io.grpc.binder.BinderChannelCredentials;
|
||||||
import io.grpc.binder.internal.Bindable.Observer;
|
import io.grpc.binder.internal.Bindable.Observer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -59,6 +63,7 @@ public final class ServiceBindingTest {
|
||||||
|
|
||||||
private Application appContext;
|
private Application appContext;
|
||||||
private ComponentName serviceComponent;
|
private ComponentName serviceComponent;
|
||||||
|
private ServiceInfo serviceInfo = new ServiceInfo();
|
||||||
private ShadowApplication shadowApplication;
|
private ShadowApplication shadowApplication;
|
||||||
private TestObserver observer;
|
private TestObserver observer;
|
||||||
private ServiceBinding binding;
|
private ServiceBinding binding;
|
||||||
|
@ -67,13 +72,17 @@ public final class ServiceBindingTest {
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
appContext = ApplicationProvider.getApplicationContext();
|
appContext = ApplicationProvider.getApplicationContext();
|
||||||
serviceComponent = new ComponentName("DUMMY", "SERVICE");
|
serviceComponent = new ComponentName("DUMMY", "SERVICE");
|
||||||
|
serviceInfo.packageName = serviceComponent.getPackageName();
|
||||||
|
serviceInfo.name = serviceComponent.getClassName();
|
||||||
observer = new TestObserver();
|
observer = new TestObserver();
|
||||||
|
|
||||||
shadowApplication = shadowOf(appContext);
|
shadowApplication = shadowOf(appContext);
|
||||||
shadowApplication.setComponentNameAndServiceForBindService(serviceComponent, mockBinder);
|
shadowApplication.setComponentNameAndServiceForBindService(serviceComponent, mockBinder);
|
||||||
|
shadowOf(appContext.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||||
|
|
||||||
// Don't call onServiceDisconnected() upon unbindService(), just like the real Android doesn't.
|
// Don't call onServiceDisconnected() upon unbindService(), just like the real Android doesn't.
|
||||||
shadowApplication.setUnbindServiceCallsOnServiceDisconnected(false);
|
shadowApplication.setUnbindServiceCallsOnServiceDisconnected(false);
|
||||||
|
shadowApplication.setBindServiceCallsOnServiceConnectedDirectly(false);
|
||||||
|
|
||||||
binding = newBuilder().build();
|
binding = newBuilder().build();
|
||||||
shadowOf(getMainLooper()).idle();
|
shadowOf(getMainLooper()).idle();
|
||||||
|
@ -276,6 +285,93 @@ public final class ServiceBindingTest {
|
||||||
assertThat(binding.isSourceContextCleared()).isFalse();
|
assertThat(binding.isSourceContextCleared()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolve() throws Exception {
|
||||||
|
serviceInfo.processName = "x"; // ServiceInfo has no equals() so look for one distinctive field.
|
||||||
|
shadowOf(appContext.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||||
|
ServiceInfo resolvedServiceInfo = binding.resolve();
|
||||||
|
assertThat(resolvedServiceInfo.processName).isEqualTo(serviceInfo.processName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 33)
|
||||||
|
public void testResolveWithTargetUserHandle() throws Exception {
|
||||||
|
serviceInfo.processName = "x"; // ServiceInfo has no equals() so look for one distinctive field.
|
||||||
|
// Robolectric just ignores the user arg to resolveServiceAsUser() so this is all we can do.
|
||||||
|
shadowOf(appContext.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||||
|
binding = newBuilder().setTargetUserHandle(generateUserHandle(/* userId= */ 0)).build();
|
||||||
|
ServiceInfo resolvedServiceInfo = binding.resolve();
|
||||||
|
assertThat(resolvedServiceInfo.processName).isEqualTo(serviceInfo.processName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveNonExistentServiceThrows() throws Exception {
|
||||||
|
ComponentName doesNotExistService = new ComponentName("does.not.exist", "NoService");
|
||||||
|
binding = newBuilder().setTargetComponent(doesNotExistService).build();
|
||||||
|
StatusException statusException = assertThrows(StatusException.class, binding::resolve);
|
||||||
|
assertThat(statusException.getStatus().getCode()).isEqualTo(Code.UNIMPLEMENTED);
|
||||||
|
assertThat(statusException.getStatus().getDescription()).contains("does.not.exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 33)
|
||||||
|
public void testResolveNonExistentServiceWithTargetUserThrows() throws Exception {
|
||||||
|
ComponentName doesNotExistService = new ComponentName("does.not.exist", "NoService");
|
||||||
|
binding =
|
||||||
|
newBuilder()
|
||||||
|
.setTargetUserHandle(generateUserHandle(/* userId= */ 12345))
|
||||||
|
.setTargetComponent(doesNotExistService)
|
||||||
|
.build();
|
||||||
|
StatusException statusException = assertThrows(StatusException.class, binding::resolve);
|
||||||
|
assertThat(statusException.getStatus().getCode()).isEqualTo(Code.UNIMPLEMENTED);
|
||||||
|
assertThat(statusException.getStatus().getDescription()).contains("does.not.exist");
|
||||||
|
assertThat(statusException.getStatus().getDescription()).contains("12345");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 30)
|
||||||
|
public void testBindService_doesNotThrowInternalErrorWhenSdkAtLeastR() {
|
||||||
|
UserHandle userHandle = generateUserHandle(/* userId= */ 12345);
|
||||||
|
binding = newBuilder().setTargetUserHandle(userHandle).build();
|
||||||
|
binding.bind();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
assertThat(Build.VERSION.SDK_INT).isEqualTo(Build.VERSION_CODES.R);
|
||||||
|
assertThat(observer.unboundReason).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 28)
|
||||||
|
public void testBindServiceAsUser_returnsErrorWhenSdkBelowR() {
|
||||||
|
UserHandle userHandle = generateUserHandle(/* userId= */ 12345);
|
||||||
|
binding = newBuilder().setTargetUserHandle(userHandle).build();
|
||||||
|
binding.bind();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.INTERNAL);
|
||||||
|
assertThat(observer.unboundReason.getDescription())
|
||||||
|
.isEqualTo("Cross user Channel requires Android R+");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 28)
|
||||||
|
public void testDevicePolicyBlind_returnsErrorWhenSdkBelowR() {
|
||||||
|
String deviceAdminClassName = "DevicePolicyAdmin";
|
||||||
|
ComponentName adminComponent = new ComponentName(appContext, deviceAdminClassName);
|
||||||
|
allowBindDeviceAdminForUser(appContext, adminComponent, 10);
|
||||||
|
binding =
|
||||||
|
newBuilder()
|
||||||
|
.setTargetUserHandle(UserHandle.getUserHandleForUid(10))
|
||||||
|
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
|
||||||
|
.build();
|
||||||
|
binding.bind();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.INTERNAL);
|
||||||
|
assertThat(observer.unboundReason.getDescription())
|
||||||
|
.isEqualTo("Device policy admin binding requires Android R+");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = 30)
|
@Config(sdk = 30)
|
||||||
public void testBindWithDeviceAdmin() throws Exception {
|
public void testBindWithDeviceAdmin() throws Exception {
|
||||||
|
@ -284,7 +380,7 @@ public final class ServiceBindingTest {
|
||||||
allowBindDeviceAdminForUser(appContext, adminComponent, /* userId= */ 0);
|
allowBindDeviceAdminForUser(appContext, adminComponent, /* userId= */ 0);
|
||||||
binding =
|
binding =
|
||||||
newBuilder()
|
newBuilder()
|
||||||
.setTargetUserHandle(UserHandle.getUserHandleForUid(/* userId= */ 0))
|
.setTargetUserHandle(UserHandle.getUserHandleForUid(/* uid= */ 0))
|
||||||
.setTargetUserHandle(generateUserHandle(/* userId= */ 0))
|
.setTargetUserHandle(generateUserHandle(/* userId= */ 0))
|
||||||
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
|
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import io.grpc.ChannelLogger;
|
||||||
|
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||||
|
import io.grpc.internal.TestUtils.NoopChannelLogger;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helps unit tests create {@link BinderClientTransport} instances without having to mention
|
||||||
|
* irrelevant details (go/tott/719).
|
||||||
|
*/
|
||||||
|
public class BinderClientTransportBuilder {
|
||||||
|
private BinderClientTransportFactory factory;
|
||||||
|
private SocketAddress serverAddress;
|
||||||
|
private ChannelLogger channelLogger = new NoopChannelLogger();
|
||||||
|
private io.grpc.internal.ClientTransportFactory.ClientTransportOptions options =
|
||||||
|
new ClientTransportOptions();
|
||||||
|
|
||||||
|
public BinderClientTransportBuilder setServerAddress(SocketAddress serverAddress) {
|
||||||
|
this.serverAddress = checkNotNull(serverAddress);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinderClientTransportBuilder setChannelLogger(ChannelLogger channelLogger) {
|
||||||
|
this.channelLogger = checkNotNull(channelLogger);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinderClientTransportBuilder setOptions(ClientTransportOptions options) {
|
||||||
|
this.options = checkNotNull(options);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinderClientTransportBuilder setFactory(BinderClientTransportFactory factory) {
|
||||||
|
this.factory = checkNotNull(factory);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinderClientTransport build() {
|
||||||
|
return factory.newClientTransport(
|
||||||
|
checkNotNull(serverAddress), checkNotNull(options), checkNotNull(channelLogger));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.binder.AsyncSecurityPolicy;
|
||||||
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AsyncSecurityPolicy} that lets unit tests verify the exact order of authorization
|
||||||
|
* requests and respond to them one at a time.
|
||||||
|
*/
|
||||||
|
public class SettableAsyncSecurityPolicy extends AsyncSecurityPolicy {
|
||||||
|
private final LinkedBlockingDeque<AuthRequest> pendingRequests = new LinkedBlockingDeque<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
|
||||||
|
AuthRequest request = new AuthRequest(uid);
|
||||||
|
pendingRequests.add(request);
|
||||||
|
return request.resultFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the next "check authorization" request to be made and returns it, throwing in case no
|
||||||
|
* request arrives in time.
|
||||||
|
*/
|
||||||
|
public AuthRequest takeNextAuthRequest(long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException, TimeoutException {
|
||||||
|
AuthRequest nextAuthRequest = pendingRequests.poll(timeout, unit);
|
||||||
|
if (nextAuthRequest == null) {
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
return nextAuthRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents a single call to {@link AsyncSecurityPolicy#checkAuthorizationAsync(int)}. */
|
||||||
|
public static class AuthRequest {
|
||||||
|
|
||||||
|
/** The argument passed to {@link AsyncSecurityPolicy#checkAuthorizationAsync(int)}. */
|
||||||
|
public final int uid;
|
||||||
|
|
||||||
|
private final SettableFuture<Status> resultFuture = SettableFuture.create();
|
||||||
|
|
||||||
|
private AuthRequest(int uid) {
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides this SecurityPolicy's response to this authorization request. */
|
||||||
|
public void setResult(Status result) {
|
||||||
|
checkState(resultFuture.set(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Simulates an exceptional response to this authorization request. */
|
||||||
|
public void setResult(Throwable t) {
|
||||||
|
checkState(resultFuture.setException(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests if the future returned for this authorization request was cancelled by the caller. */
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return resultFuture.isCancelled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
build.gradle
20
build.gradle
|
@ -21,11 +21,11 @@ subprojects {
|
||||||
apply plugin: "net.ltgt.errorprone"
|
apply plugin: "net.ltgt.errorprone"
|
||||||
|
|
||||||
group = "io.grpc"
|
group = "io.grpc"
|
||||||
version = "1.73.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
version = "1.76.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { // The google mirror is less flaky than mavenCentral()
|
maven { // The google mirror is less flaky than mavenCentral()
|
||||||
url "https://maven-central.storage-download.googleapis.com/maven2/"
|
url = "https://maven-central.storage-download.googleapis.com/maven2/"
|
||||||
metadataSources {
|
metadataSources {
|
||||||
mavenPom()
|
mavenPom()
|
||||||
ignoreGradleMetadataRedirection()
|
ignoreGradleMetadataRedirection()
|
||||||
|
@ -241,9 +241,9 @@ subprojects {
|
||||||
tasks.named("test").configure {
|
tasks.named("test").configure {
|
||||||
testLogging {
|
testLogging {
|
||||||
exceptionFormat = 'full'
|
exceptionFormat = 'full'
|
||||||
showExceptions true
|
showExceptions = true
|
||||||
showCauses true
|
showCauses = true
|
||||||
showStackTraces true
|
showStackTraces = true
|
||||||
}
|
}
|
||||||
maxHeapSize = '1500m'
|
maxHeapSize = '1500m'
|
||||||
}
|
}
|
||||||
|
@ -389,11 +389,11 @@ subprojects {
|
||||||
url = new File(rootProject.repositoryDir).toURI()
|
url = new File(rootProject.repositoryDir).toURI()
|
||||||
} else {
|
} else {
|
||||||
String stagingUrl
|
String stagingUrl
|
||||||
|
String baseUrl = "https://ossrh-staging-api.central.sonatype.com/service/local"
|
||||||
if (rootProject.hasProperty('repositoryId')) {
|
if (rootProject.hasProperty('repositoryId')) {
|
||||||
stagingUrl = 'https://oss.sonatype.org/service/local/staging/deployByRepositoryId/' +
|
stagingUrl = "${baseUrl}/staging/deployByRepositoryId/" + rootProject.repositoryId
|
||||||
rootProject.repositoryId
|
|
||||||
} else {
|
} else {
|
||||||
stagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
|
stagingUrl = "${baseUrl}/staging/deploy/maven2/"
|
||||||
}
|
}
|
||||||
credentials {
|
credentials {
|
||||||
if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) {
|
if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) {
|
||||||
|
@ -402,7 +402,7 @@ subprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def releaseUrl = stagingUrl
|
def releaseUrl = stagingUrl
|
||||||
def snapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
|
def snapshotUrl = 'https://central.sonatype.com/repository/maven-snapshots/'
|
||||||
url = version.endsWith('SNAPSHOT') ? snapshotUrl : releaseUrl
|
url = version.endsWith('SNAPSHOT') ? snapshotUrl : releaseUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -410,7 +410,7 @@ subprojects {
|
||||||
}
|
}
|
||||||
|
|
||||||
signing {
|
signing {
|
||||||
required false
|
required = false
|
||||||
sign publishing.publications.maven
|
sign publishing.publications.maven
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,11 @@ RUN mkdir -p "$ANDROID_HOME/cmdline-tools" && \
|
||||||
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" && \
|
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" && \
|
||||||
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses
|
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses
|
||||||
|
|
||||||
|
RUN curl -Ls https://github.com/Kitware/CMake/releases/download/v3.26.3/cmake-3.26.3-linux-x86_64.tar.gz | \
|
||||||
|
tar xz -C /var/local
|
||||||
|
|
||||||
# Install Maven
|
# Install Maven
|
||||||
RUN curl -Ls https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz | \
|
RUN curl -Ls https://archive.apache.org/dist/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz | \
|
||||||
tar xz -C /var/local
|
tar xz -C /var/local
|
||||||
ENV PATH /var/local/apache-maven-3.8.8/bin:$PATH
|
ENV PATH /var/local/cmake-3.26.3-linux-x86_64/bin:/var/local/apache-maven-3.8.8/bin:$PATH
|
||||||
|
|
||||||
|
|
|
@ -10,5 +10,11 @@ RUN export DEBIAN_FRONTEND=noninteractive && \
|
||||||
g++-aarch64-linux-gnu \
|
g++-aarch64-linux-gnu \
|
||||||
g++-powerpc64le-linux-gnu \
|
g++-powerpc64le-linux-gnu \
|
||||||
openjdk-8-jdk \
|
openjdk-8-jdk \
|
||||||
|
pkg-config \
|
||||||
&& \
|
&& \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN curl -Ls https://github.com/Kitware/CMake/releases/download/v3.26.3/cmake-3.26.3-linux-x86_64.tar.gz | \
|
||||||
|
tar xz -C /var/local
|
||||||
|
ENV PATH /var/local/cmake-3.26.3-linux-x86_64/bin:$PATH
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,11 @@ RUN export DEBIAN_FRONTEND=noninteractive && \
|
||||||
curl \
|
curl \
|
||||||
g++-s390x-linux-gnu \
|
g++-s390x-linux-gnu \
|
||||||
openjdk-8-jdk \
|
openjdk-8-jdk \
|
||||||
|
pkg-config \
|
||||||
&& \
|
&& \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN curl -Ls https://github.com/Kitware/CMake/releases/download/v3.26.3/cmake-3.26.3-linux-x86_64.tar.gz | \
|
||||||
|
tar xz -C /var/local
|
||||||
|
ENV PATH /var/local/cmake-3.26.3-linux-x86_64/bin:$PATH
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,8 @@
|
||||||
|
|
||||||
set -exu -o pipefail
|
set -exu -o pipefail
|
||||||
|
|
||||||
# Install gRPC and codegen for the Android interop app
|
|
||||||
# (a composite gradle build can't find protoc-gen-grpc-java)
|
|
||||||
|
|
||||||
cd github/grpc-java
|
cd github/grpc-java
|
||||||
|
|
||||||
export LDFLAGS=-L/tmp/protobuf/lib
|
|
||||||
export CXXFLAGS=-I/tmp/protobuf/include
|
|
||||||
export LD_LIBRARY_PATH=/tmp/protobuf/lib
|
|
||||||
export OS_NAME=$(uname)
|
|
||||||
|
|
||||||
export ANDROID_HOME=/tmp/Android/Sdk
|
export ANDROID_HOME=/tmp/Android/Sdk
|
||||||
mkdir -p "${ANDROID_HOME}/cmdline-tools"
|
mkdir -p "${ANDROID_HOME}/cmdline-tools"
|
||||||
curl -Ls -o cmdline.zip \
|
curl -Ls -o cmdline.zip \
|
||||||
|
@ -21,15 +13,12 @@ rm cmdline.zip
|
||||||
mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest"
|
mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest"
|
||||||
(yes || true) | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --licenses
|
(yes || true) | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --licenses
|
||||||
|
|
||||||
# Proto deps
|
|
||||||
buildscripts/make_dependencies.sh
|
|
||||||
|
|
||||||
# Build Android with Java 11, this adds it to the PATH
|
# Build Android with Java 11, this adds it to the PATH
|
||||||
sudo update-java-alternatives --set java-1.11.0-openjdk-amd64
|
sudo update-java-alternatives --set java-1.11.0-openjdk-amd64
|
||||||
# Unset any existing JAVA_HOME env var to stop Gradle from using it
|
# Unset any existing JAVA_HOME env var to stop Gradle from using it
|
||||||
unset JAVA_HOME
|
unset JAVA_HOME
|
||||||
|
|
||||||
GRADLE_FLAGS="-Pandroid.useAndroidX=true -Dorg.gradle.jvmargs=-Xmx1024m"
|
GRADLE_FLAGS="-Pandroid.useAndroidX=true -Dorg.gradle.jvmargs=-Xmx1024m -PskipCodegen=true"
|
||||||
|
|
||||||
./gradlew $GRADLE_FLAGS :grpc-android-interop-testing:assembleDebug
|
./gradlew $GRADLE_FLAGS :grpc-android-interop-testing:assembleDebug
|
||||||
./gradlew $GRADLE_FLAGS :grpc-android-interop-testing:assembleDebugAndroidTest
|
./gradlew $GRADLE_FLAGS :grpc-android-interop-testing:assembleDebugAndroidTest
|
||||||
|
|
|
@ -9,9 +9,6 @@ BASE_DIR="$(pwd)"
|
||||||
|
|
||||||
cd "$BASE_DIR/github/grpc-java"
|
cd "$BASE_DIR/github/grpc-java"
|
||||||
|
|
||||||
export LDFLAGS=-L/tmp/protobuf/lib
|
|
||||||
export CXXFLAGS=-I/tmp/protobuf/include
|
|
||||||
export LD_LIBRARY_PATH=/tmp/protobuf/lib
|
|
||||||
export OS_NAME=$(uname)
|
export OS_NAME=$(uname)
|
||||||
|
|
||||||
cat <<EOF >> gradle.properties
|
cat <<EOF >> gradle.properties
|
||||||
|
@ -30,10 +27,18 @@ unzip -qd "${ANDROID_HOME}/cmdline-tools" cmdline.zip
|
||||||
rm cmdline.zip
|
rm cmdline.zip
|
||||||
mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest"
|
mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest"
|
||||||
(yes || true) | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --licenses
|
(yes || true) | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --licenses
|
||||||
|
curl -Ls https://github.com/Kitware/CMake/releases/download/v3.26.3/cmake-3.26.3-linux-x86_64.tar.gz | \
|
||||||
|
tar xz -C /tmp
|
||||||
|
export PATH=/tmp/cmake-3.26.3-linux-x86_64/bin:$PATH
|
||||||
|
|
||||||
# Proto deps
|
# Proto deps
|
||||||
buildscripts/make_dependencies.sh
|
buildscripts/make_dependencies.sh
|
||||||
|
|
||||||
|
sudo apt-get update && sudo apt-get install pkg-config
|
||||||
|
export LDFLAGS="$(PKG_CONFIG_PATH=/tmp/protobuf/lib/pkgconfig pkg-config --libs protobuf)"
|
||||||
|
export CXXFLAGS="$(PKG_CONFIG_PATH=/tmp/protobuf/lib/pkgconfig pkg-config --cflags protobuf)"
|
||||||
|
export LD_LIBRARY_PATH=/tmp/protobuf/lib
|
||||||
|
|
||||||
# Build Android with Java 11, this adds it to the PATH
|
# Build Android with Java 11, this adds it to the PATH
|
||||||
sudo update-java-alternatives --set java-1.11.0-openjdk-amd64
|
sudo update-java-alternatives --set java-1.11.0-openjdk-amd64
|
||||||
# Unset any existing JAVA_HOME env var to stop Gradle from using it
|
# Unset any existing JAVA_HOME env var to stop Gradle from using it
|
||||||
|
@ -98,6 +103,7 @@ cd $BASE_DIR/github/grpc-java
|
||||||
./gradlew clean
|
./gradlew clean
|
||||||
git checkout HEAD^
|
git checkout HEAD^
|
||||||
./gradlew --stop # use a new daemon to build the previous commit
|
./gradlew --stop # use a new daemon to build the previous commit
|
||||||
|
GRADLE_FLAGS="${GRADLE_FLAGS} -PskipCodegen=true" # skip codegen for build from previous commit since it wasn't built with --std=c++14 when making this change
|
||||||
./gradlew publishToMavenLocal $GRADLE_FLAGS
|
./gradlew publishToMavenLocal $GRADLE_FLAGS
|
||||||
cd examples/android/helloworld/
|
cd examples/android/helloworld/
|
||||||
../../gradlew build $GRADLE_FLAGS
|
../../gradlew build $GRADLE_FLAGS
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Config file for internal CI
|
||||||
|
|
||||||
|
# Location of the continuous shell script in repository.
|
||||||
|
build_file: "grpc-java/buildscripts/kokoro/psm-interop-test-java.sh"
|
||||||
|
timeout_mins: 240
|
||||||
|
|
||||||
|
action {
|
||||||
|
define_artifacts {
|
||||||
|
regex: "artifacts/**/*sponge_log.xml"
|
||||||
|
regex: "artifacts/**/*.log"
|
||||||
|
strip_prefix: "artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env_vars {
|
||||||
|
key: "PSM_TEST_SUITE"
|
||||||
|
value: "cloud_run"
|
||||||
|
}
|
|
@ -13,5 +13,5 @@ action {
|
||||||
}
|
}
|
||||||
env_vars {
|
env_vars {
|
||||||
key: "PSM_TEST_SUITE"
|
key: "PSM_TEST_SUITE"
|
||||||
value: "fallback"
|
value: "light"
|
||||||
}
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Config file for internal CI
|
||||||
|
|
||||||
|
# Location of the continuous shell script in repository.
|
||||||
|
build_file: "grpc-java/buildscripts/kokoro/psm-interop-test-java.sh"
|
||||||
|
timeout_mins: 240
|
||||||
|
|
||||||
|
action {
|
||||||
|
define_artifacts {
|
||||||
|
regex: "artifacts/**/*sponge_log.xml"
|
||||||
|
regex: "artifacts/**/*.log"
|
||||||
|
strip_prefix: "artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env_vars {
|
||||||
|
key: "PSM_TEST_SUITE"
|
||||||
|
value: "spiffe"
|
||||||
|
}
|
|
@ -51,9 +51,9 @@ fi
|
||||||
export GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1g'"
|
export GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1g'"
|
||||||
|
|
||||||
# Make protobuf discoverable by :grpc-compiler
|
# Make protobuf discoverable by :grpc-compiler
|
||||||
export LD_LIBRARY_PATH=/tmp/protobuf/lib
|
export LDFLAGS="$(PKG_CONFIG_PATH=/tmp/protobuf/lib/pkgconfig pkg-config --libs protobuf)"
|
||||||
export LDFLAGS=-L/tmp/protobuf/lib
|
export CXXFLAGS="$(PKG_CONFIG_PATH=/tmp/protobuf/lib/pkgconfig pkg-config --cflags protobuf)"
|
||||||
export CXXFLAGS="-I/tmp/protobuf/include"
|
export LIBRARY_PATH=/tmp/protobuf/lib
|
||||||
|
|
||||||
./gradlew grpc-compiler:clean $GRADLE_FLAGS
|
./gradlew grpc-compiler:clean $GRADLE_FLAGS
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Location of the continuous shell script in repository.
|
# Location of the continuous shell script in repository.
|
||||||
build_file: "grpc-java/buildscripts/kokoro/windows.bat"
|
build_file: "grpc-java/buildscripts/kokoro/windows.bat"
|
||||||
timeout_mins: 45
|
timeout_mins: 90
|
||||||
|
|
||||||
# We always build mvn artifacts.
|
# We always build mvn artifacts.
|
||||||
action {
|
action {
|
||||||
|
|
|
@ -15,19 +15,21 @@ set ESCWORKSPACE=%WORKSPACE:\=\\%
|
||||||
|
|
||||||
@rem Clear JAVA_HOME to prevent a different Java version from being used
|
@rem Clear JAVA_HOME to prevent a different Java version from being used
|
||||||
set JAVA_HOME=
|
set JAVA_HOME=
|
||||||
set PATH=C:\Program Files\OpenJDK\openjdk-11.0.12_7\bin;%PATH%
|
|
||||||
|
|
||||||
mkdir grpc-java-helper32
|
mkdir grpc-java-helper32
|
||||||
cd grpc-java-helper32
|
cd grpc-java-helper32
|
||||||
call "%VS140COMNTOOLS%\vsvars32.bat" || exit /b 1
|
call "%VS170COMNTOOLS%\..\..\VC\Auxiliary\Build\vcvars32.bat" || exit /b 1
|
||||||
call "%WORKSPACE%\buildscripts\make_dependencies.bat" || exit /b 1
|
call "%WORKSPACE%\buildscripts\make_dependencies.bat" || exit /b 1
|
||||||
|
|
||||||
cd "%WORKSPACE%"
|
cd "%WORKSPACE%"
|
||||||
|
|
||||||
SET TARGET_ARCH=x86_32
|
SET TARGET_ARCH=x86_32
|
||||||
SET FAIL_ON_WARNINGS=true
|
SET FAIL_ON_WARNINGS=true
|
||||||
SET VC_PROTOBUF_LIBS=%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\build\\Release
|
SET PROTOBUF_VER=22.5
|
||||||
SET VC_PROTOBUF_INCLUDE=%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\build\\include
|
SET PKG_CONFIG_PATH=%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\build\\protobuf-%PROTOBUF_VER%\\lib\\pkgconfig
|
||||||
|
SET VC_PROTOBUF_LIBS=/LIBPATH:%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\build\\protobuf-%PROTOBUF_VER%\\lib
|
||||||
|
SET VC_PROTOBUF_INCLUDE=%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\build\\protobuf-%PROTOBUF_VER%\\include
|
||||||
|
call :Get_Libs
|
||||||
SET GRADLE_FLAGS=-PtargetArch=%TARGET_ARCH% -PfailOnWarnings=%FAIL_ON_WARNINGS% -PvcProtobufLibs=%VC_PROTOBUF_LIBS% -PvcProtobufInclude=%VC_PROTOBUF_INCLUDE% -PskipAndroid=true
|
SET GRADLE_FLAGS=-PtargetArch=%TARGET_ARCH% -PfailOnWarnings=%FAIL_ON_WARNINGS% -PvcProtobufLibs=%VC_PROTOBUF_LIBS% -PvcProtobufInclude=%VC_PROTOBUF_INCLUDE% -PskipAndroid=true
|
||||||
SET GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1g'"
|
SET GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1g'"
|
||||||
|
|
||||||
|
@ -50,3 +52,32 @@ IF NOT %GRADLEEXIT% == 0 (
|
||||||
cmd.exe /C "%WORKSPACE%\gradlew.bat --stop"
|
cmd.exe /C "%WORKSPACE%\gradlew.bat --stop"
|
||||||
|
|
||||||
cmd.exe /C "%WORKSPACE%\gradlew.bat %GRADLE_FLAGS% -Dorg.gradle.parallel=false -PrepositoryDir=%WORKSPACE%\artifacts clean grpc-compiler:build grpc-compiler:publish" || exit /b 1
|
cmd.exe /C "%WORKSPACE%\gradlew.bat %GRADLE_FLAGS% -Dorg.gradle.parallel=false -PrepositoryDir=%WORKSPACE%\artifacts clean grpc-compiler:build grpc-compiler:publish" || exit /b 1
|
||||||
|
|
||||||
|
goto :eof
|
||||||
|
:Get_Libs
|
||||||
|
SetLocal EnableDelayedExpansion
|
||||||
|
set "libs_list="
|
||||||
|
for /f "tokens=*" %%a in ('pkg-config --libs protobuf') do (
|
||||||
|
for %%b in (%%a) do (
|
||||||
|
set lib=%%b
|
||||||
|
set libfirst2char=!lib:~0,2!
|
||||||
|
if !libfirst2char!==-l (
|
||||||
|
@rem remove the leading -l
|
||||||
|
set lib=!lib:~2!
|
||||||
|
@rem remove spaces
|
||||||
|
set lib=!lib: =!
|
||||||
|
@rem Because protobuf is specified as libprotobuf and elsewhere
|
||||||
|
if !lib! NEQ protobuf (
|
||||||
|
set lib=!lib!.lib
|
||||||
|
if "!libs_list!"=="" (
|
||||||
|
set libs_list=!lib!
|
||||||
|
) else (
|
||||||
|
set libs_list=!libs_list!,!lib!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
EndLocal & set "VC_PROTOBUF_LIBS=%VC_PROTOBUF_LIBS%,%libs_list%"
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
|
|
@ -14,19 +14,21 @@ set ESCWORKSPACE=%WORKSPACE:\=\\%
|
||||||
|
|
||||||
@rem Clear JAVA_HOME to prevent a different Java version from being used
|
@rem Clear JAVA_HOME to prevent a different Java version from being used
|
||||||
set JAVA_HOME=
|
set JAVA_HOME=
|
||||||
set PATH=C:\Program Files\OpenJDK\openjdk-11.0.12_7\bin;%PATH%
|
|
||||||
|
|
||||||
mkdir grpc-java-helper64
|
mkdir grpc-java-helper64
|
||||||
cd grpc-java-helper64
|
cd grpc-java-helper64
|
||||||
call "%VS140COMNTOOLS%\..\..\VC\bin\amd64\vcvars64.bat" || exit /b 1
|
call "%VS170COMNTOOLS%\..\..\VC\Auxiliary\Build\vcvars64.bat" || exit /b 1
|
||||||
call "%WORKSPACE%\buildscripts\make_dependencies.bat" || exit /b 1
|
call "%WORKSPACE%\buildscripts\make_dependencies.bat" || exit /b 1
|
||||||
|
|
||||||
cd "%WORKSPACE%"
|
cd "%WORKSPACE%"
|
||||||
|
|
||||||
SET TARGET_ARCH=x86_64
|
SET TARGET_ARCH=x86_64
|
||||||
SET FAIL_ON_WARNINGS=true
|
SET FAIL_ON_WARNINGS=true
|
||||||
SET VC_PROTOBUF_LIBS=%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\build\\Release
|
SET PROTOBUF_VER=22.5
|
||||||
SET VC_PROTOBUF_INCLUDE=%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\build\\include
|
SET PKG_CONFIG_PATH=%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\build\\protobuf-%PROTOBUF_VER%\\lib\\pkgconfig
|
||||||
|
SET VC_PROTOBUF_LIBS=/LIBPATH:%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\build\\protobuf-%PROTOBUF_VER%\\lib
|
||||||
|
SET VC_PROTOBUF_INCLUDE=%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\build\\protobuf-%PROTOBUF_VER%\\include
|
||||||
|
call :Get_Libs
|
||||||
SET GRADLE_FLAGS=-PtargetArch=%TARGET_ARCH% -PfailOnWarnings=%FAIL_ON_WARNINGS% -PvcProtobufLibs=%VC_PROTOBUF_LIBS% -PvcProtobufInclude=%VC_PROTOBUF_INCLUDE% -PskipAndroid=true
|
SET GRADLE_FLAGS=-PtargetArch=%TARGET_ARCH% -PfailOnWarnings=%FAIL_ON_WARNINGS% -PvcProtobufLibs=%VC_PROTOBUF_LIBS% -PvcProtobufInclude=%VC_PROTOBUF_INCLUDE% -PskipAndroid=true
|
||||||
SET GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1g'"
|
SET GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1g'"
|
||||||
|
|
||||||
|
@ -34,3 +36,32 @@ SET GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1g'"
|
||||||
cmd.exe /C "%WORKSPACE%\gradlew.bat --stop"
|
cmd.exe /C "%WORKSPACE%\gradlew.bat --stop"
|
||||||
|
|
||||||
cmd.exe /C "%WORKSPACE%\gradlew.bat %GRADLE_FLAGS% -Dorg.gradle.parallel=false -PrepositoryDir=%WORKSPACE%\artifacts grpc-compiler:clean grpc-compiler:build grpc-compiler:publish" || exit /b 1
|
cmd.exe /C "%WORKSPACE%\gradlew.bat %GRADLE_FLAGS% -Dorg.gradle.parallel=false -PrepositoryDir=%WORKSPACE%\artifacts grpc-compiler:clean grpc-compiler:build grpc-compiler:publish" || exit /b 1
|
||||||
|
|
||||||
|
goto :eof
|
||||||
|
:Get_Libs
|
||||||
|
SetLocal EnableDelayedExpansion
|
||||||
|
set "libs_list="
|
||||||
|
for /f "tokens=*" %%a in ('pkg-config --libs protobuf') do (
|
||||||
|
for %%b in (%%a) do (
|
||||||
|
set lib=%%b
|
||||||
|
set libfirst2char=!lib:~0,2!
|
||||||
|
if !libfirst2char!==-l (
|
||||||
|
@rem remove the leading -l
|
||||||
|
set lib=!lib:~2!
|
||||||
|
@rem remove spaces
|
||||||
|
set lib=!lib: =!
|
||||||
|
@rem Because protobuf is specified as libprotobuf and elsewhere
|
||||||
|
if !lib! NEQ protobuf (
|
||||||
|
set lib=!lib!.lib
|
||||||
|
if "!libs_list!"=="" (
|
||||||
|
set libs_list=!lib!
|
||||||
|
) else (
|
||||||
|
set libs_list=!libs_list!,!lib!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
EndLocal & set "VC_PROTOBUF_LIBS=%VC_PROTOBUF_LIBS%,%libs_list%"
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
set PROTOBUF_VER=21.7
|
choco install -y pkgconfiglite
|
||||||
set CMAKE_NAME=cmake-3.3.2-win32-x86
|
choco install -y openjdk --version=17.0
|
||||||
|
set PATH=%PATH%;"c:\Program Files\OpenJDK\jdk-17\bin"
|
||||||
|
set PROTOBUF_VER=22.5
|
||||||
|
set ABSL_VERSION=20230125.4
|
||||||
|
set CMAKE_NAME=cmake-3.26.3-windows-x86_64
|
||||||
|
|
||||||
if not exist "protobuf-%PROTOBUF_VER%\build\Release\" (
|
if not exist "protobuf-%PROTOBUF_VER%\build\Release\" (
|
||||||
call :installProto || exit /b 1
|
call :installProto || exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
echo Compile gRPC-Java with something like:
|
echo Compile gRPC-Java with something like:
|
||||||
echo -PtargetArch=x86_32 -PvcProtobufLibs=%cd%\protobuf-%PROTOBUF_VER%\build\Release -PvcProtobufInclude=%cd%\protobuf-%PROTOBUF_VER%\build\include
|
echo -PtargetArch=x86_32 -PvcProtobufLibPath=%cd%\protobuf-%PROTOBUF_VER%\build\protobuf-%PROTOBUF_VER%\lib -PvcProtobufInclude=%cd%\protobuf-%PROTOBUF_VER%\build\protobuf-%PROTOBUF_VER%\include -PvcProtobufLibs=insert-list-of-libs-from-pkg-config-output-here
|
||||||
goto :eof
|
goto :eof
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,25 +24,35 @@ if not exist "%CMAKE_NAME%" (
|
||||||
set PATH=%PATH%;%cd%\%CMAKE_NAME%\bin
|
set PATH=%PATH%;%cd%\%CMAKE_NAME%\bin
|
||||||
:hasCmake
|
:hasCmake
|
||||||
@rem GitHub requires TLSv1.2, and for whatever reason our powershell doesn't have it enabled
|
@rem GitHub requires TLSv1.2, and for whatever reason our powershell doesn't have it enabled
|
||||||
powershell -command "$ErrorActionPreference = 'stop'; & { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; iwr https://github.com/google/protobuf/archive/v%PROTOBUF_VER%.zip -OutFile protobuf.zip }" || exit /b 1
|
powershell -command "$ProgressPreference = 'SilentlyContinue'; $ErrorActionPreference = 'stop'; & { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; iwr https://github.com/google/protobuf/releases/download/v%PROTOBUF_VER%/protobuf-%PROTOBUF_VER%.zip -OutFile protobuf.zip }" || exit /b 1
|
||||||
powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('protobuf.zip', '.') }" || exit /b 1
|
powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('protobuf.zip', '.') }" || exit /b 1
|
||||||
del protobuf.zip
|
del protobuf.zip
|
||||||
|
powershell -command "$ProgressPreference = 'SilentlyContinue'; $ErrorActionPreference = 'stop'; & { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; iwr https://github.com/abseil/abseil-cpp/archive/refs/tags/%ABSL_VERSION%.zip -OutFile absl.zip }" || exit /b 1
|
||||||
|
powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('absl.zip', '.') }" || exit /b 1
|
||||||
|
del absl.zip
|
||||||
|
rmdir protobuf-%PROTOBUF_VER%\third_party\abseil-cpp
|
||||||
|
move abseil-cpp-%ABSL_VERSION% protobuf-%PROTOBUF_VER%\third_party\abseil-cpp
|
||||||
mkdir protobuf-%PROTOBUF_VER%\build
|
mkdir protobuf-%PROTOBUF_VER%\build
|
||||||
pushd protobuf-%PROTOBUF_VER%\build
|
pushd protobuf-%PROTOBUF_VER%\build
|
||||||
|
|
||||||
@rem Workaround https://github.com/protocolbuffers/protobuf/issues/10174
|
|
||||||
powershell -command "(Get-Content ..\cmake\extract_includes.bat.in) -replace '\.\.\\', '' | Out-File -encoding ascii ..\cmake\extract_includes.bat.in"
|
|
||||||
@rem cmake does not detect x86_64 from the vcvars64.bat variables.
|
@rem cmake does not detect x86_64 from the vcvars64.bat variables.
|
||||||
@rem If vcvars64.bat has set PLATFORM to X64, then inform cmake to use the Win64 version of VS
|
@rem If vcvars64.bat has set PLATFORM to X64, then inform cmake to use the Win64 version of VS, likewise for x32
|
||||||
if "%PLATFORM%" == "X64" (
|
if "%PLATFORM%" == "x64" (
|
||||||
@rem Note the space
|
SET CMAKE_VSARCH=-A x64
|
||||||
SET CMAKE_VSARCH= Win64
|
) else if "%PLATFORM%" == "x86" (
|
||||||
|
@rem -A x86 doesn't work: https://github.com/microsoft/vcpkg/issues/15465
|
||||||
|
SET CMAKE_VSARCH=-DCMAKE_GENERATOR_PLATFORM=WIN32
|
||||||
) else (
|
) else (
|
||||||
SET CMAKE_VSARCH=
|
SET CMAKE_VSARCH=
|
||||||
)
|
)
|
||||||
cmake -Dprotobuf_BUILD_TESTS=OFF -G "Visual Studio %VisualStudioVersion:~0,2%%CMAKE_VSARCH%" .. || exit /b 1
|
for /f "tokens=4 delims=\" %%a in ("%VCINSTALLDIR%") do (
|
||||||
msbuild /maxcpucount /p:Configuration=Release /verbosity:minimal libprotoc.vcxproj || exit /b 1
|
SET VC_YEAR=%%a
|
||||||
call extract_includes.bat || exit /b 1
|
)
|
||||||
|
for /f "tokens=1 delims=." %%a in ("%VisualStudioVersion%") do (
|
||||||
|
SET visual_studio_major_version=%%a
|
||||||
|
)
|
||||||
|
cmake -Dprotobuf_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=%cd%\protobuf-%PROTOBUF_VER% -DCMAKE_PREFIX_PATH=%cd%\protobuf-%PROTOBUF_VER% -G "Visual Studio %visual_studio_major_version% %VC_YEAR%" %CMAKE_VSARCH% .. || exit /b 1
|
||||||
|
cmake --build . --config Release --target install || exit /b 1
|
||||||
popd
|
popd
|
||||||
goto :eof
|
goto :eof
|
||||||
|
|
||||||
|
@ -49,3 +63,4 @@ powershell -command "$ErrorActionPreference = 'stop'; & { iwr https://cmake.org/
|
||||||
powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('cmake.zip', '.') }" || exit /b 1
|
powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('cmake.zip', '.') }" || exit /b 1
|
||||||
del cmake.zip
|
del cmake.zip
|
||||||
goto :eof
|
goto :eof
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,17 @@
|
||||||
# Build protoc
|
# Build protoc
|
||||||
set -evux -o pipefail
|
set -evux -o pipefail
|
||||||
|
|
||||||
PROTOBUF_VERSION=21.7
|
PROTOBUF_VERSION=22.5
|
||||||
|
ABSL_VERSION=20230125.4
|
||||||
|
CMAKE_VERSION=3.26.3
|
||||||
|
|
||||||
# ARCH is x86_64 bit unless otherwise specified.
|
# ARCH is x86_64 bit unless otherwise specified.
|
||||||
ARCH="${ARCH:-x86_64}"
|
ARCH="${ARCH:-x86_64}"
|
||||||
DOWNLOAD_DIR=/tmp/source
|
DOWNLOAD_DIR=/tmp/source
|
||||||
INSTALL_DIR="/tmp/protobuf-cache/$PROTOBUF_VERSION/$(uname -s)-$ARCH"
|
INSTALL_DIR="/tmp/protobuf-cache/$PROTOBUF_VERSION/$(uname -s)-$ARCH"
|
||||||
|
BUILDSCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
mkdir -p $DOWNLOAD_DIR
|
mkdir -p $DOWNLOAD_DIR
|
||||||
|
cd "$DOWNLOAD_DIR"
|
||||||
|
|
||||||
# Start with a sane default
|
# Start with a sane default
|
||||||
NUM_CPU=4
|
NUM_CPU=4
|
||||||
|
@ -26,27 +30,46 @@ if [ -f ${INSTALL_DIR}/bin/protoc ]; then
|
||||||
echo "Not building protobuf. Already built"
|
echo "Not building protobuf. Already built"
|
||||||
# TODO(ejona): swap to `brew install --devel protobuf` once it is up-to-date
|
# TODO(ejona): swap to `brew install --devel protobuf` once it is up-to-date
|
||||||
else
|
else
|
||||||
if [[ ! -d "$DOWNLOAD_DIR"/protobuf-"${PROTOBUF_VERSION}" ]]; then
|
if [[ ! -d "protobuf-${PROTOBUF_VERSION}" ]]; then
|
||||||
curl -Ls https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-all-${PROTOBUF_VERSION}.tar.gz | tar xz -C $DOWNLOAD_DIR
|
curl -Ls "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-${PROTOBUF_VERSION}.tar.gz" | tar xz
|
||||||
fi
|
curl -Ls "https://github.com/abseil/abseil-cpp/archive/refs/tags/${ABSL_VERSION}.tar.gz" | tar xz
|
||||||
pushd $DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION}
|
rmdir "protobuf-$PROTOBUF_VERSION/third_party/abseil-cpp"
|
||||||
# install here so we don't need sudo
|
mv "abseil-cpp-$ABSL_VERSION" "protobuf-$PROTOBUF_VERSION/third_party/abseil-cpp"
|
||||||
if [[ "$ARCH" == x86* ]]; then
|
|
||||||
./configure CFLAGS=-m${ARCH#*_} CXXFLAGS=-m${ARCH#*_} --disable-shared \
|
|
||||||
--prefix="$INSTALL_DIR"
|
|
||||||
elif [[ "$ARCH" == aarch* ]]; then
|
|
||||||
./configure --disable-shared --host=aarch64-linux-gnu --prefix="$INSTALL_DIR"
|
|
||||||
elif [[ "$ARCH" == ppc* ]]; then
|
|
||||||
./configure --disable-shared --host=powerpc64le-linux-gnu --prefix="$INSTALL_DIR"
|
|
||||||
elif [[ "$ARCH" == s390* ]]; then
|
|
||||||
./configure --disable-shared --host=s390x-linux-gnu --prefix="$INSTALL_DIR"
|
|
||||||
elif [[ "$ARCH" == loongarch* ]]; then
|
|
||||||
./configure --disable-shared --host=loongarch64-unknown-linux-gnu --prefix="$INSTALL_DIR"
|
|
||||||
fi
|
fi
|
||||||
# the same source dir is used for 32 and 64 bit builds, so we need to clean stale data first
|
# the same source dir is used for 32 and 64 bit builds, so we need to clean stale data first
|
||||||
make clean
|
rm -rf "$DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION}/build"
|
||||||
make V=0 -j$NUM_CPU
|
mkdir "$DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION}/build"
|
||||||
make install
|
pushd "$DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION}/build"
|
||||||
|
# install here so we don't need sudo
|
||||||
|
if [[ "$ARCH" == x86* ]]; then
|
||||||
|
CFLAGS=-m${ARCH#*_} CXXFLAGS=-m${ARCH#*_} cmake .. \
|
||||||
|
-DCMAKE_CXX_STANDARD=14 -Dprotobuf_BUILD_TESTS=OFF -DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" -DABSL_INTERNAL_AT_LEAST_CXX17=0 \
|
||||||
|
-B. || exit 1
|
||||||
|
else
|
||||||
|
if [[ "$ARCH" == aarch_64 ]]; then
|
||||||
|
GCC_ARCH=aarch64-linux-gnu
|
||||||
|
elif [[ "$ARCH" == ppcle_64 ]]; then
|
||||||
|
GCC_ARCH=powerpc64le-linux-gnu
|
||||||
|
elif [[ "$ARCH" == s390_64 ]]; then
|
||||||
|
GCC_ARCH=s390x-linux-gnu
|
||||||
|
elif [[ "$ARCH" == loongarch_64 ]]; then
|
||||||
|
GCC_ARCH=loongarch64-unknown-linux-gnu
|
||||||
|
else
|
||||||
|
echo "Unknown architecture: $ARCH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cmake .. \
|
||||||
|
-DCMAKE_CXX_STANDARD=14 -Dprotobuf_BUILD_TESTS=OFF -DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" -DABSL_INTERNAL_AT_LEAST_CXX17=0 \
|
||||||
|
-Dcrosscompile_ARCH="$GCC_ARCH" \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE=$BUILDSCRIPTS_DIR/toolchain.cmake \
|
||||||
|
-B. || exit 1
|
||||||
|
fi
|
||||||
|
export CMAKE_BUILD_PARALLEL_LEVEL="$NUM_CPU"
|
||||||
|
cmake --build . || exit 1
|
||||||
|
cmake --install . || exit 1
|
||||||
|
[ -d "$INSTALL_DIR/lib64" ] && mv "$INSTALL_DIR/lib64" "$INSTALL_DIR/lib"
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -60,7 +83,8 @@ ln -s "$INSTALL_DIR" /tmp/protobuf
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
To compile with the build dependencies:
|
To compile with the build dependencies:
|
||||||
|
|
||||||
export LDFLAGS=-L/tmp/protobuf/lib
|
export LDFLAGS="$(PKG_CONFIG_PATH=/tmp/protobuf/lib/pkgconfig pkg-config --libs protobuf)"
|
||||||
export CXXFLAGS=-I/tmp/protobuf/include
|
export CXXFLAGS="$(PKG_CONFIG_PATH=/tmp/protobuf/lib/pkgconfig pkg-config --cflags protobuf)"
|
||||||
|
export LIBRARY_PATH=/tmp/protobuf/lib
|
||||||
export LD_LIBRARY_PATH=/tmp/protobuf/lib
|
export LD_LIBRARY_PATH=/tmp/protobuf/lib
|
||||||
EOF
|
EOF
|
||||||
|
|
|
@ -59,7 +59,7 @@ if [ -z "$USERNAME" -o -z "$PASSWORD" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
STAGING_URL="https://oss.sonatype.org/service/local/staging"
|
STAGING_URL="https://ossrh-staging-api.central.sonatype.com/service/local/staging"
|
||||||
|
|
||||||
# We go through the effort of using deloyByRepositoryId/ because it is
|
# We go through the effort of using deloyByRepositoryId/ because it is
|
||||||
# _substantially_ faster to upload files than deploy/maven2/. When using
|
# _substantially_ faster to upload files than deploy/maven2/. When using
|
||||||
|
@ -108,3 +108,18 @@ XML="
|
||||||
</promoteRequest>"
|
</promoteRequest>"
|
||||||
curl --fail-with-body -X POST -d "$XML" -u "$USERPASS" -H "Content-Type: application/xml" \
|
curl --fail-with-body -X POST -d "$XML" -u "$USERPASS" -H "Content-Type: application/xml" \
|
||||||
"$STAGING_URL/profiles/$PROFILE_ID/finish"
|
"$STAGING_URL/profiles/$PROFILE_ID/finish"
|
||||||
|
|
||||||
|
# TODO (okshiva): After 2-3 releases make it automatic.
|
||||||
|
# After closing the repository on the staging API, we must manually trigger
|
||||||
|
# its upload to the main Central Publisher Portal. We set publishing_type=automatic
|
||||||
|
# to have it release automatically upon passing validation.
|
||||||
|
# echo "Triggering release of repository ${REPOID} to the Central Portal"
|
||||||
|
|
||||||
|
# MANUAL_API_URL="https://ossrh-staging-api.central.sonatype.com/service/local/manual"
|
||||||
|
|
||||||
|
#curl --fail-with-body -X POST \
|
||||||
|
# -H "Authorization: Bearer ${USERPASS}" \
|
||||||
|
# -H "Content-Type: application/json" \
|
||||||
|
# "${MANUAL_API_URL}/upload/repository/${REPOID}?publishing_type=automatic"
|
||||||
|
|
||||||
|
# echo "Release triggered. Monitor progress at https://central.sonatype.com/publishing/deployments"
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
set(CMAKE_SYSTEM_NAME Linux)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER "${crosscompile_ARCH}-gcc")
|
||||||
|
set(CMAKE_CXX_COMPILER "${crosscompile_ARCH}-g++")
|
||||||
|
set(CMAKE_FIND_ROOT_PATH "/usr/${crosscompile_ARCH}/")
|
||||||
|
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
|
@ -40,7 +40,7 @@ dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("javadoc").configure {
|
tasks.named("javadoc").configure {
|
||||||
failOnError false // no public or protected classes found to document
|
failOnError = false // no public or protected classes found to document
|
||||||
exclude 'io/grpc/census/internal/**'
|
exclude 'io/grpc/census/internal/**'
|
||||||
exclude 'io/grpc/census/Internal*'
|
exclude 'io/grpc/census/Internal*'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.census;
|
||||||
|
|
||||||
|
import com.google.common.base.Stopwatch;
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
import io.grpc.ClientInterceptor;
|
||||||
|
import io.grpc.ExperimentalApi;
|
||||||
|
import io.grpc.ManagedChannelBuilder;
|
||||||
|
import io.grpc.ServerBuilder;
|
||||||
|
import io.grpc.ServerStreamTracer;
|
||||||
|
import io.opencensus.trace.Tracing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entrypoint for OpenCensus instrumentation functionality in gRPC.
|
||||||
|
*
|
||||||
|
* <p>GrpcCensus uses {@link io.opencensus.api.OpenCensus} APIs for instrumentation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12178")
|
||||||
|
public final class GrpcCensus {
|
||||||
|
|
||||||
|
private final boolean statsEnabled;
|
||||||
|
private final boolean tracingEnabled;
|
||||||
|
|
||||||
|
private GrpcCensus(Builder builder) {
|
||||||
|
this.statsEnabled = builder.statsEnabled;
|
||||||
|
this.tracingEnabled = builder.tracingEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new builder for {@link GrpcCensus}.
|
||||||
|
*/
|
||||||
|
public static Builder newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Supplier<Stopwatch> STOPWATCH_SUPPLIER = new Supplier<Stopwatch>() {
|
||||||
|
@Override
|
||||||
|
public Stopwatch get() {
|
||||||
|
return Stopwatch.createUnstarted();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a {@link ServerBuilder} to enable census stats and tracing.
|
||||||
|
*
|
||||||
|
* @param serverBuilder The server builder to configure.
|
||||||
|
* @return The configured server builder.
|
||||||
|
*/
|
||||||
|
public <T extends ServerBuilder<T>> T configureServerBuilder(T serverBuilder) {
|
||||||
|
if (statsEnabled) {
|
||||||
|
serverBuilder.addStreamTracerFactory(newServerStatsStreamTracerFactory());
|
||||||
|
}
|
||||||
|
if (tracingEnabled) {
|
||||||
|
serverBuilder.addStreamTracerFactory(newServerTracingStreamTracerFactory());
|
||||||
|
}
|
||||||
|
return serverBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a {@link ManagedChannelBuilder} to enable census stats and tracing.
|
||||||
|
*
|
||||||
|
* @param channelBuilder The channel builder to configure.
|
||||||
|
* @return The configured channel builder.
|
||||||
|
*/
|
||||||
|
public <T extends ManagedChannelBuilder<T>> T configureChannelBuilder(T channelBuilder) {
|
||||||
|
if (statsEnabled) {
|
||||||
|
channelBuilder.intercept(newClientStatsInterceptor());
|
||||||
|
}
|
||||||
|
if (tracingEnabled) {
|
||||||
|
channelBuilder.intercept(newClientTracingInterceptor());
|
||||||
|
}
|
||||||
|
return channelBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link ClientInterceptor} with default stats implementation.
|
||||||
|
*/
|
||||||
|
private static ClientInterceptor newClientStatsInterceptor() {
|
||||||
|
CensusStatsModule censusStats =
|
||||||
|
new CensusStatsModule(
|
||||||
|
STOPWATCH_SUPPLIER,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true);
|
||||||
|
return censusStats.getClientInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link ClientInterceptor} with default tracing implementation.
|
||||||
|
*/
|
||||||
|
private static ClientInterceptor newClientTracingInterceptor() {
|
||||||
|
CensusTracingModule censusTracing =
|
||||||
|
new CensusTracingModule(
|
||||||
|
Tracing.getTracer(),
|
||||||
|
Tracing.getPropagationComponent().getBinaryFormat());
|
||||||
|
return censusTracing.getClientInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link ServerStreamTracer.Factory} with default stats implementation.
|
||||||
|
*/
|
||||||
|
private static ServerStreamTracer.Factory newServerStatsStreamTracerFactory() {
|
||||||
|
CensusStatsModule censusStats =
|
||||||
|
new CensusStatsModule(
|
||||||
|
STOPWATCH_SUPPLIER,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true);
|
||||||
|
return censusStats.getServerTracerFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link ServerStreamTracer.Factory} with default tracing implementation.
|
||||||
|
*/
|
||||||
|
private static ServerStreamTracer.Factory newServerTracingStreamTracerFactory() {
|
||||||
|
CensusTracingModule censusTracing =
|
||||||
|
new CensusTracingModule(
|
||||||
|
Tracing.getTracer(),
|
||||||
|
Tracing.getPropagationComponent().getBinaryFormat());
|
||||||
|
return censusTracing.getServerTracerFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for {@link GrpcCensus}.
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
private boolean statsEnabled = true;
|
||||||
|
private boolean tracingEnabled = true;
|
||||||
|
|
||||||
|
private Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables stats collection.
|
||||||
|
*/
|
||||||
|
public Builder disableStats() {
|
||||||
|
this.statsEnabled = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables tracing.
|
||||||
|
*/
|
||||||
|
public Builder disableTracing() {
|
||||||
|
this.tracingEnabled = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a new {@link GrpcCensus}.
|
||||||
|
*/
|
||||||
|
public GrpcCensus build() {
|
||||||
|
return new GrpcCensus(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,11 +18,11 @@ cc_binary(
|
||||||
|
|
||||||
java_library(
|
java_library(
|
||||||
name = "java_grpc_library_deps__do_not_reference",
|
name = "java_grpc_library_deps__do_not_reference",
|
||||||
|
visibility = ["//xds:__pkg__"],
|
||||||
exports = [
|
exports = [
|
||||||
"//api",
|
"//api",
|
||||||
"//protobuf",
|
"//protobuf",
|
||||||
"//stub",
|
"//stub",
|
||||||
"//stub:javax_annotation",
|
|
||||||
artifact("com.google.code.findbugs:jsr305"),
|
artifact("com.google.code.findbugs:jsr305"),
|
||||||
artifact("com.google.guava:guava"),
|
artifact("com.google.guava:guava"),
|
||||||
"@com_google_protobuf//:protobuf_java",
|
"@com_google_protobuf//:protobuf_java",
|
||||||
|
@ -35,7 +35,6 @@ java_library(
|
||||||
"//api",
|
"//api",
|
||||||
"//protobuf-lite",
|
"//protobuf-lite",
|
||||||
"//stub",
|
"//stub",
|
||||||
"//stub:javax_annotation",
|
|
||||||
artifact("com.google.code.findbugs:jsr305"),
|
artifact("com.google.code.findbugs:jsr305"),
|
||||||
artifact("com.google.guava:guava"),
|
artifact("com.google.guava:guava"),
|
||||||
],
|
],
|
||||||
|
|
|
@ -100,19 +100,20 @@ model {
|
||||||
all {
|
all {
|
||||||
if (toolChain in Gcc || toolChain in Clang) {
|
if (toolChain in Gcc || toolChain in Clang) {
|
||||||
cppCompiler.define("GRPC_VERSION", version)
|
cppCompiler.define("GRPC_VERSION", version)
|
||||||
cppCompiler.args "--std=c++0x"
|
cppCompiler.args "--std=c++14"
|
||||||
addEnvArgs("CXXFLAGS", cppCompiler.args)
|
addEnvArgs("CXXFLAGS", cppCompiler.args)
|
||||||
addEnvArgs("CPPFLAGS", cppCompiler.args)
|
addEnvArgs("CPPFLAGS", cppCompiler.args)
|
||||||
if (osdetector.os == "osx") {
|
if (osdetector.os == "osx") {
|
||||||
cppCompiler.args "-mmacosx-version-min=10.7", "-stdlib=libc++"
|
cppCompiler.args "-mmacosx-version-min=10.7", "-stdlib=libc++"
|
||||||
|
linker.args "-framework", "CoreFoundation"
|
||||||
addLibraryIfNotLinked('protoc', linker.args)
|
addLibraryIfNotLinked('protoc', linker.args)
|
||||||
addLibraryIfNotLinked('protobuf', linker.args)
|
addLibraryIfNotLinked('protobuf', linker.args)
|
||||||
} else if (osdetector.os == "windows") {
|
} else if (osdetector.os == "windows") {
|
||||||
linker.args "-static", "-lprotoc", "-lprotobuf", "-static-libgcc", "-static-libstdc++",
|
linker.args "-static", "-lprotoc", "-lprotobuf", "-static-libgcc", "-static-libstdc++",
|
||||||
"-s"
|
"-s"
|
||||||
} else if (osdetector.arch == "ppcle_64") {
|
} else if (osdetector.arch == "ppcle_64") {
|
||||||
linker.args "-Wl,-Bstatic", "-lprotoc", "-lprotobuf", "-Wl,-Bdynamic", "-lpthread", "-s"
|
linker.args "-Wl,-Bstatic", "-lprotoc", "-lprotobuf", "-Wl,-Bdynamic", "-lpthread", "-s"
|
||||||
} else {
|
} else {
|
||||||
// Link protoc, protobuf, libgcc and libstdc++ statically.
|
// Link protoc, protobuf, libgcc and libstdc++ statically.
|
||||||
// Link other (system) libraries dynamically.
|
// Link other (system) libraries dynamically.
|
||||||
// Clang under OSX doesn't support these options.
|
// Clang under OSX doesn't support these options.
|
||||||
|
@ -126,10 +127,12 @@ model {
|
||||||
cppCompiler.args "/EHsc", "/MT"
|
cppCompiler.args "/EHsc", "/MT"
|
||||||
if (rootProject.hasProperty('vcProtobufInclude')) {
|
if (rootProject.hasProperty('vcProtobufInclude')) {
|
||||||
cppCompiler.args "/I${rootProject.vcProtobufInclude}"
|
cppCompiler.args "/I${rootProject.vcProtobufInclude}"
|
||||||
}
|
}
|
||||||
linker.args "libprotobuf.lib", "libprotoc.lib"
|
linker.args.add("libprotoc.lib")
|
||||||
|
linker.args.add("libprotobuf.lib")
|
||||||
if (rootProject.hasProperty('vcProtobufLibs')) {
|
if (rootProject.hasProperty('vcProtobufLibs')) {
|
||||||
linker.args "/LIBPATH:${rootProject.vcProtobufLibs}"
|
String libsList = rootProject.property('vcProtobufLibs') as String
|
||||||
|
libsList.split(',').each() { lib -> linker.args.add(lib) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +187,11 @@ protobuf {
|
||||||
inputs.file javaPluginPath
|
inputs.file javaPluginPath
|
||||||
}
|
}
|
||||||
ofSourceSet('test').configureEach {
|
ofSourceSet('test').configureEach {
|
||||||
plugins { grpc {} }
|
plugins {
|
||||||
|
grpc {
|
||||||
|
option '@generated=javax'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ofSourceSet('testLite').configureEach {
|
ofSourceSet('testLite').configureEach {
|
||||||
builtins {
|
builtins {
|
||||||
|
@ -193,7 +200,6 @@ protobuf {
|
||||||
plugins {
|
plugins {
|
||||||
grpc {
|
grpc {
|
||||||
option 'lite'
|
option 'lite'
|
||||||
option '@generated=omit'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,9 +245,10 @@ def checkArtifacts = tasks.register("checkArtifacts") {
|
||||||
if (ret.exitValue != 0) {
|
if (ret.exitValue != 0) {
|
||||||
throw new GradleException("dumpbin exited with " + ret.exitValue)
|
throw new GradleException("dumpbin exited with " + ret.exitValue)
|
||||||
}
|
}
|
||||||
def dlls = os.toString() =~ /Image has the following dependencies:\s+(.*)\s+Summary/
|
def dlls_match_results = os.toString() =~ /Image has the following dependencies:([\S\s]*)Summary/
|
||||||
if (dlls[0][1] != "KERNEL32.dll") {
|
def dlls = dlls_match_results[0][1].trim().split("\\s+").sort()
|
||||||
throw new Exception("unexpected dll deps: " + dlls[0][1]);
|
if (dlls != ["KERNEL32.dll", "dbghelp.dll"]) {
|
||||||
|
throw new Exception("unexpected dll deps: " + dlls);
|
||||||
}
|
}
|
||||||
os.reset()
|
os.reset()
|
||||||
ret = exec {
|
ret = exec {
|
||||||
|
|
|
@ -114,7 +114,7 @@ checkDependencies ()
|
||||||
white_list="KERNEL32\.dll\|msvcrt\.dll\|USER32\.dll"
|
white_list="KERNEL32\.dll\|msvcrt\.dll\|USER32\.dll"
|
||||||
elif [[ "$OS" == linux ]]; then
|
elif [[ "$OS" == linux ]]; then
|
||||||
dump_cmd='objdump -x '"$1"' | grep "NEEDED"'
|
dump_cmd='objdump -x '"$1"' | grep "NEEDED"'
|
||||||
white_list="libpthread\.so\.0\|libstdc++\.so\.6\|libc\.so\.6"
|
white_list="libpthread\.so\.0\|libstdc++\.so\.6\|libc\.so\.6\|librt\.so\.1\|libm\.so\.6"
|
||||||
if [[ "$ARCH" == x86_32 ]]; then
|
if [[ "$ARCH" == x86_32 ]]; then
|
||||||
white_list="${white_list}\|libm\.so\.6"
|
white_list="${white_list}\|libm\.so\.6"
|
||||||
elif [[ "$ARCH" == x86_64 ]]; then
|
elif [[ "$ARCH" == x86_64 ]]; then
|
||||||
|
|
|
@ -745,9 +745,10 @@ static void PrintStub(
|
||||||
" $lower_method_name$($input_type$ request)");
|
" $lower_method_name$($input_type$ request)");
|
||||||
} else {
|
} else {
|
||||||
// Simple RPC
|
// Simple RPC
|
||||||
|
(*vars)["throws_decl"] = " throws io.grpc.StatusException";
|
||||||
p->Print(
|
p->Print(
|
||||||
*vars,
|
*vars,
|
||||||
"$output_type$ $lower_method_name$($input_type$ request)");
|
"$output_type$ $lower_method_name$($input_type$ request)$throws_decl$");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ASYNC_CALL:
|
case ASYNC_CALL:
|
||||||
|
@ -827,7 +828,8 @@ static void PrintStub(
|
||||||
if (server_streaming) {
|
if (server_streaming) {
|
||||||
(*vars)["calls_method"] = "io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall";
|
(*vars)["calls_method"] = "io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall";
|
||||||
} else {
|
} else {
|
||||||
(*vars)["calls_method"] = "io.grpc.stub.ClientCalls.blockingUnaryCall";
|
(*vars)["calls_method"] = "io.grpc.stub.ClientCalls.blockingV2UnaryCall";
|
||||||
|
(*vars)["throws_decl"] = " throws io.grpc.StatusException";
|
||||||
}
|
}
|
||||||
|
|
||||||
p->Print(
|
p->Print(
|
||||||
|
|
|
@ -80,7 +80,7 @@ class JavaGrpcGenerator : public protobuf::compiler::CodeGenerator {
|
||||||
java_grpc_generator::ProtoFlavor flavor =
|
java_grpc_generator::ProtoFlavor flavor =
|
||||||
java_grpc_generator::ProtoFlavor::NORMAL;
|
java_grpc_generator::ProtoFlavor::NORMAL;
|
||||||
java_grpc_generator::GeneratedAnnotation generated_annotation =
|
java_grpc_generator::GeneratedAnnotation generated_annotation =
|
||||||
java_grpc_generator::GeneratedAnnotation::JAVAX;
|
java_grpc_generator::GeneratedAnnotation::OMIT;
|
||||||
|
|
||||||
bool disable_version = false;
|
bool disable_version = false;
|
||||||
for (size_t i = 0; i < options.size(); i++) {
|
for (size_t i = 0; i < options.size(); i++) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
@javax.annotation.Generated(
|
||||||
value = "by gRPC proto compiler (version 1.73.0-SNAPSHOT)",
|
value = "by gRPC proto compiler (version 1.76.0-SNAPSHOT)",
|
||||||
comments = "Source: grpc/testing/compiler/test.proto")
|
comments = "Source: grpc/testing/compiler/test.proto")
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
@java.lang.Deprecated
|
@java.lang.Deprecated
|
||||||
|
@ -203,8 +203,8 @@ public final class TestDeprecatedServiceGrpc {
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@java.lang.Deprecated
|
@java.lang.Deprecated
|
||||||
public io.grpc.testing.compiler.Test.SimpleResponse deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request) {
|
public io.grpc.testing.compiler.Test.SimpleResponse deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getDeprecatedMethodMethod(), getCallOptions(), request);
|
getChannel(), getDeprecatedMethodMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
@javax.annotation.Generated(
|
||||||
value = "by gRPC proto compiler (version 1.73.0-SNAPSHOT)",
|
value = "by gRPC proto compiler (version 1.76.0-SNAPSHOT)",
|
||||||
comments = "Source: grpc/testing/compiler/test.proto")
|
comments = "Source: grpc/testing/compiler/test.proto")
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class TestServiceGrpc {
|
public final class TestServiceGrpc {
|
||||||
|
@ -580,8 +580,8 @@ public final class TestServiceGrpc {
|
||||||
* The server returns the client payload as-is.
|
* The server returns the client payload as-is.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.compiler.Test.SimpleResponse unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request) {
|
public io.grpc.testing.compiler.Test.SimpleResponse unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -658,8 +658,8 @@ public final class TestServiceGrpc {
|
||||||
* A unary call that is Safe.
|
* A unary call that is Safe.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.compiler.Test.SimpleResponse safeCall(io.grpc.testing.compiler.Test.SimpleRequest request) {
|
public io.grpc.testing.compiler.Test.SimpleResponse safeCall(io.grpc.testing.compiler.Test.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getSafeCallMethod(), getCallOptions(), request);
|
getChannel(), getSafeCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,8 +668,8 @@ public final class TestServiceGrpc {
|
||||||
* A unary call that is Idempotent.
|
* A unary call that is Idempotent.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.compiler.Test.SimpleResponse idempotentCall(io.grpc.testing.compiler.Test.SimpleRequest request) {
|
public io.grpc.testing.compiler.Test.SimpleResponse idempotentCall(io.grpc.testing.compiler.Test.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getIdempotentCallMethod(), getCallOptions(), request);
|
getChannel(), getIdempotentCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,8 +199,8 @@ public final class TestDeprecatedServiceGrpc {
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@java.lang.Deprecated
|
@java.lang.Deprecated
|
||||||
public io.grpc.testing.compiler.Test.SimpleResponse deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request) {
|
public io.grpc.testing.compiler.Test.SimpleResponse deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getDeprecatedMethodMethod(), getCallOptions(), request);
|
getChannel(), getDeprecatedMethodMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -569,8 +569,8 @@ public final class TestServiceGrpc {
|
||||||
* The server returns the client payload as-is.
|
* The server returns the client payload as-is.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.compiler.Test.SimpleResponse unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request) {
|
public io.grpc.testing.compiler.Test.SimpleResponse unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,8 +647,8 @@ public final class TestServiceGrpc {
|
||||||
* A unary call that is Safe.
|
* A unary call that is Safe.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.compiler.Test.SimpleResponse safeCall(io.grpc.testing.compiler.Test.SimpleRequest request) {
|
public io.grpc.testing.compiler.Test.SimpleResponse safeCall(io.grpc.testing.compiler.Test.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getSafeCallMethod(), getCallOptions(), request);
|
getChannel(), getSafeCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,8 +657,8 @@ public final class TestServiceGrpc {
|
||||||
* A unary call that is Idempotent.
|
* A unary call that is Idempotent.
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public io.grpc.testing.compiler.Test.SimpleResponse idempotentCall(io.grpc.testing.compiler.Test.SimpleRequest request) {
|
public io.grpc.testing.compiler.Test.SimpleResponse idempotentCall(io.grpc.testing.compiler.Test.SimpleRequest request) throws io.grpc.StatusException {
|
||||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||||
getChannel(), getIdempotentCallMethod(), getCallOptions(), request);
|
getChannel(), getIdempotentCallMethod(), getCallOptions(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import static com.google.common.base.Preconditions.checkState;
|
||||||
import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY;
|
import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY;
|
||||||
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
|
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
|
||||||
import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
|
import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
|
||||||
import static java.lang.Math.max;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
@ -124,8 +123,7 @@ public abstract class AbstractClientStream extends AbstractStream
|
||||||
@Override
|
@Override
|
||||||
public void setDeadline(Deadline deadline) {
|
public void setDeadline(Deadline deadline) {
|
||||||
headers.discardAll(TIMEOUT_KEY);
|
headers.discardAll(TIMEOUT_KEY);
|
||||||
long effectiveTimeout = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
headers.put(TIMEOUT_KEY, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||||
headers.put(TIMEOUT_KEY, effectiveTimeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -96,15 +96,13 @@ public class DelayedClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
|
||||||
private ScheduledFuture<?> scheduleDeadlineIfNeeded(
|
private ScheduledFuture<?> scheduleDeadlineIfNeeded(
|
||||||
ScheduledExecutorService scheduler, @Nullable Deadline deadline) {
|
ScheduledExecutorService scheduler, @Nullable Deadline deadline) {
|
||||||
Deadline contextDeadline = context.getDeadline();
|
Deadline contextDeadline = context.getDeadline();
|
||||||
if (deadline == null && contextDeadline == null) {
|
String deadlineName;
|
||||||
return null;
|
long remainingNanos;
|
||||||
}
|
if (deadline != null && isAbeforeB(deadline, contextDeadline)) {
|
||||||
long remainingNanos = Long.MAX_VALUE;
|
deadlineName = "CallOptions";
|
||||||
if (deadline != null) {
|
|
||||||
remainingNanos = deadline.timeRemaining(NANOSECONDS);
|
remainingNanos = deadline.timeRemaining(NANOSECONDS);
|
||||||
}
|
} else if (contextDeadline != null) {
|
||||||
|
deadlineName = "Context";
|
||||||
if (contextDeadline != null && contextDeadline.timeRemaining(NANOSECONDS) < remainingNanos) {
|
|
||||||
remainingNanos = contextDeadline.timeRemaining(NANOSECONDS);
|
remainingNanos = contextDeadline.timeRemaining(NANOSECONDS);
|
||||||
if (logger.isLoggable(Level.FINE)) {
|
if (logger.isLoggable(Level.FINE)) {
|
||||||
StringBuilder builder =
|
StringBuilder builder =
|
||||||
|
@ -121,29 +119,29 @@ public class DelayedClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
|
||||||
}
|
}
|
||||||
logger.fine(builder.toString());
|
logger.fine(builder.toString());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
long seconds = Math.abs(remainingNanos) / TimeUnit.SECONDS.toNanos(1);
|
|
||||||
long nanos = Math.abs(remainingNanos) % TimeUnit.SECONDS.toNanos(1);
|
|
||||||
final StringBuilder buf = new StringBuilder();
|
|
||||||
String deadlineName = isAbeforeB(contextDeadline, deadline) ? "Context" : "CallOptions";
|
|
||||||
if (remainingNanos < 0) {
|
|
||||||
buf.append("ClientCall started after ");
|
|
||||||
buf.append(deadlineName);
|
|
||||||
buf.append(" deadline was exceeded. Deadline has been exceeded for ");
|
|
||||||
} else {
|
} else {
|
||||||
buf.append("Deadline ");
|
return null;
|
||||||
buf.append(deadlineName);
|
|
||||||
buf.append(" will be exceeded in ");
|
|
||||||
}
|
}
|
||||||
buf.append(seconds);
|
|
||||||
buf.append(String.format(Locale.US, ".%09d", nanos));
|
|
||||||
buf.append("s. ");
|
|
||||||
|
|
||||||
/* Cancels the call if deadline exceeded prior to the real call being set. */
|
/* Cancels the call if deadline exceeded prior to the real call being set. */
|
||||||
class DeadlineExceededRunnable implements Runnable {
|
class DeadlineExceededRunnable implements Runnable {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
long seconds = Math.abs(remainingNanos) / TimeUnit.SECONDS.toNanos(1);
|
||||||
|
long nanos = Math.abs(remainingNanos) % TimeUnit.SECONDS.toNanos(1);
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
if (remainingNanos < 0) {
|
||||||
|
buf.append("ClientCall started after ");
|
||||||
|
buf.append(deadlineName);
|
||||||
|
buf.append(" deadline was exceeded. Deadline has been exceeded for ");
|
||||||
|
} else {
|
||||||
|
buf.append("Deadline ");
|
||||||
|
buf.append(deadlineName);
|
||||||
|
buf.append(" was exceeded after ");
|
||||||
|
}
|
||||||
|
buf.append(seconds);
|
||||||
|
buf.append(String.format(Locale.US, ".%09d", nanos));
|
||||||
|
buf.append("s");
|
||||||
cancel(
|
cancel(
|
||||||
Status.DEADLINE_EXCEEDED.withDescription(buf.toString()),
|
Status.DEADLINE_EXCEEDED.withDescription(buf.toString()),
|
||||||
// We should not cancel the call if the realCall is set because there could be a
|
// We should not cancel the call if the realCall is set because there could be a
|
||||||
|
|
|
@ -219,7 +219,7 @@ public final class GrpcUtil {
|
||||||
|
|
||||||
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
|
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
|
||||||
|
|
||||||
public static final String IMPLEMENTATION_VERSION = "1.73.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
|
public static final String IMPLEMENTATION_VERSION = "1.76.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default timeout in nanos for a keepalive ping request.
|
* The default timeout in nanos for a keepalive ping request.
|
||||||
|
@ -651,12 +651,14 @@ public final class GrpcUtil {
|
||||||
static class TimeoutMarshaller implements Metadata.AsciiMarshaller<Long> {
|
static class TimeoutMarshaller implements Metadata.AsciiMarshaller<Long> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toAsciiString(Long timeoutNanos) {
|
public String toAsciiString(Long timeoutNanosObject) {
|
||||||
long cutoff = 100000000;
|
long cutoff = 100000000;
|
||||||
|
// Timeout checking is inherently racy. RPCs with timeouts in the past ideally don't even get
|
||||||
|
// here, but if the timeout is expired assume that happened recently and adjust it to the
|
||||||
|
// smallest allowed timeout
|
||||||
|
long timeoutNanos = Math.max(1, timeoutNanosObject);
|
||||||
TimeUnit unit = TimeUnit.NANOSECONDS;
|
TimeUnit unit = TimeUnit.NANOSECONDS;
|
||||||
if (timeoutNanos < 0) {
|
if (timeoutNanos < cutoff) {
|
||||||
throw new IllegalArgumentException("Timeout too small");
|
|
||||||
} else if (timeoutNanos < cutoff) {
|
|
||||||
return timeoutNanos + "n";
|
return timeoutNanos + "n";
|
||||||
} else if (timeoutNanos < cutoff * 1000L) {
|
} else if (timeoutNanos < cutoff * 1000L) {
|
||||||
return unit.toMicros(timeoutNanos) + "u";
|
return unit.toMicros(timeoutNanos) + "u";
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue