mirror of https://github.com/grpc/grpc-java.git
Compare commits
163 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 | |
|
6baac45bd2 | |
|
454f1c5c6a | |
|
64fe061ccd | |
|
80cc988b3c | |
|
3f5fdf1266 | |
|
be0247f501 | |
|
12aaf88d86 | |
|
25199e9df9 | |
|
7952afdd56 | |
|
6cd007d0d0 | |
|
9619453799 | |
|
53de8a72ca | |
|
7a08fdb7f9 | |
|
84bd01454b | |
|
65d0bb8a4d | |
|
f79ab2f16f | |
|
a6aec2769e | |
|
2db4852e23 | |
|
54d37839a3 | |
|
aae52de3b8 | |
|
a13fca2bf2 | |
|
edc2bf7346 | |
|
5ca4d852ae | |
|
d4c46a7f1f | |
|
c8d1e6e39c | |
|
84c7713b2f | |
|
908f9f19cd | |
|
8ca7c4ef1f | |
|
c28a7e3e06 | |
|
8f6a16f846 | |
|
2448c8b6b9 | |
|
2e260a4bbc | |
|
7507a9ec06 | |
|
a332eddc13 | |
|
350f90e1a3 | |
|
3961a923ac | |
|
1958e42370 | |
|
94f8e93691 | |
|
d60e6fc251 | |
|
d2d72cda83 | |
|
bb120a8cbb | |
|
bc3c764058 | |
|
a57c14a51e | |
|
e80c197455 | |
|
e388ef3975 | |
|
b69bd64ce7 | |
|
fca1d3cf43 | |
|
2f52a00364 | |
|
2191557582 | |
|
4933cddd00 | |
|
24b9f6ff0d | |
|
61a110d962 | |
|
f3f054a0a4 | |
|
d82613a74c | |
|
ca4819ac6d | |
|
a6a041e415 | |
|
602aece081 | |
|
12197065fe | |
|
c340f4a2f3 | |
|
1a2285b527 | |
|
cdab410b81 | |
|
57124d6b29 | |
|
110c1ff0d6 | |
|
f207be39a9 | |
|
892144dcac | |
|
68d79b5130 | |
|
60f6ea7b8e | |
|
2b87b01651 | |
|
713607056e | |
|
a132123c93 |
|
@ -102,6 +102,9 @@ jobs:
|
|||
- name: Run bazel build
|
||||
run: bazelisk build //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||
|
||||
- name: Run bazel test
|
||||
run: bazelisk test //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||
|
||||
- name: Run example bazel build
|
||||
run: bazelisk build //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||
working-directory: ./examples
|
||||
|
|
|
@ -33,7 +33,6 @@ java_library(
|
|||
"//api",
|
||||
"//protobuf",
|
||||
"//stub",
|
||||
"//stub:javax_annotation",
|
||||
"@com_google_protobuf//:protobuf_java",
|
||||
artifact("com.google.code.findbugs:jsr305"),
|
||||
artifact("com.google.guava:guava"),
|
||||
|
@ -47,7 +46,6 @@ java_library(
|
|||
"//api",
|
||||
"//protobuf-lite",
|
||||
"//stub",
|
||||
"//stub:javax_annotation",
|
||||
artifact("com.google.code.findbugs:jsr305"),
|
||||
artifact("com.google.guava:guava"),
|
||||
],
|
||||
|
@ -67,6 +65,5 @@ java_library(
|
|||
visibility = ["//:__subpackages__"],
|
||||
exports = [
|
||||
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.
|
||||
|
||||
### 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:
|
||||
```
|
||||
$ 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
|
||||
$ tar xzf protobuf-all-$PROTOBUF_VERSION.tar.gz
|
||||
$ cd protobuf-$PROTOBUF_VERSION
|
||||
|
|
|
@ -11,8 +11,6 @@ for general contribution guidelines.
|
|||
- [ejona86](https://github.com/ejona86), Google LLC
|
||||
- [jdcormie](https://github.com/jdcormie), 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
|
||||
- [sergiitk](https://github.com/sergiitk), Google LLC
|
||||
- [temawi](https://github.com/temawi), Google LLC
|
||||
|
@ -26,7 +24,9 @@ for general contribution guidelines.
|
|||
- [ericgribkoff](https://github.com/ericgribkoff)
|
||||
- [jiangtaoli2016](https://github.com/jiangtaoli2016)
|
||||
- [jtattermusch](https://github.com/jtattermusch)
|
||||
- [larry-safran](https://github.com/larry-safran)
|
||||
- [louiscryan](https://github.com/louiscryan)
|
||||
- [markb74](https://github.com/markb74)
|
||||
- [nicolasnoble](https://github.com/nicolasnoble)
|
||||
- [nmittler](https://github.com/nmittler)
|
||||
- [sanjaypujare](https://github.com/sanjaypujare)
|
||||
|
|
59
MODULE.bazel
59
MODULE.bazel
|
@ -2,13 +2,13 @@ module(
|
|||
name = "grpc-java",
|
||||
compatibility_level = 0,
|
||||
repo_name = "io_grpc_grpc_java",
|
||||
version = "1.71.0-SNAPSHOT", # CURRENT_GRPC_VERSION
|
||||
version = "1.76.0-SNAPSHOT", # CURRENT_GRPC_VERSION
|
||||
)
|
||||
|
||||
# GRPC_DEPS_START
|
||||
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||
"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-oauth2-http:1.24.1",
|
||||
"com.google.auto.value:auto-value-annotations:1.11.0",
|
||||
|
@ -19,63 +19,44 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
|||
"com.google.guava:failureaccess:1.0.1",
|
||||
"com.google.guava:guava:33.3.1-android",
|
||||
"com.google.re2j:re2j:1.8",
|
||||
"com.google.s2a.proto.v2:s2a-proto:0.1.2",
|
||||
"com.google.truth:truth:1.4.2",
|
||||
"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
|
||||
"io.netty:netty-buffer:4.1.110.Final",
|
||||
"io.netty:netty-codec-http2:4.1.110.Final",
|
||||
"io.netty:netty-codec-http:4.1.110.Final",
|
||||
"io.netty:netty-codec-socks:4.1.110.Final",
|
||||
"io.netty:netty-codec:4.1.110.Final",
|
||||
"io.netty:netty-common:4.1.110.Final",
|
||||
"io.netty:netty-handler-proxy:4.1.110.Final",
|
||||
"io.netty:netty-handler:4.1.110.Final",
|
||||
"io.netty:netty-resolver:4.1.110.Final",
|
||||
"io.netty:netty-buffer:4.1.124.Final",
|
||||
"io.netty:netty-codec-http2:4.1.124.Final",
|
||||
"io.netty:netty-codec-http:4.1.124.Final",
|
||||
"io.netty:netty-codec-socks:4.1.124.Final",
|
||||
"io.netty:netty-codec:4.1.124.Final",
|
||||
"io.netty:netty-common:4.1.124.Final",
|
||||
"io.netty:netty-handler-proxy:4.1.124.Final",
|
||||
"io.netty:netty-handler:4.1.124.Final",
|
||||
"io.netty:netty-resolver:4.1.124.Final",
|
||||
"io.netty:netty-tcnative-boringssl-static: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-unix-common:4.1.110.Final",
|
||||
"io.netty:netty-transport: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.124.Final",
|
||||
"io.netty:netty-transport:4.1.124.Final",
|
||||
"io.opencensus:opencensus-api:0.31.0",
|
||||
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
||||
"io.perfmark:perfmark-api:0.27.0",
|
||||
"junit:junit:4.13.2",
|
||||
"org.apache.tomcat:annotations-api:6.0.53",
|
||||
"org.checkerframework:checker-qual:3.12.0",
|
||||
"org.codehaus.mojo:animal-sniffer-annotations:1.24",
|
||||
]
|
||||
# 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 = "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 = "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_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_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.install(
|
||||
|
@ -202,7 +183,3 @@ maven.override(
|
|||
coordinates = "io.grpc:grpc-util",
|
||||
target = "@io_grpc_grpc_java//util",
|
||||
)
|
||||
|
||||
switched_rules = use_extension("@com_google_googleapis//:extensions.bzl", "switched_rules")
|
||||
|
||||
switched_rules.use_languages(java = True)
|
||||
|
|
36
README.md
36
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
|
||||
basics](https://grpc.io/docs/languages/java/basics).
|
||||
|
||||
The [examples](https://github.com/grpc/grpc-java/tree/v1.70.0/examples) and the
|
||||
[Android example](https://github.com/grpc/grpc-java/tree/v1.70.0/examples/android)
|
||||
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.75.0/examples/android)
|
||||
are standalone projects that showcase the usage of gRPC.
|
||||
|
||||
Download
|
||||
|
@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
|||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty-shaded</artifactId>
|
||||
<version>1.70.0</version>
|
||||
<version>1.75.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
<version>1.70.0</version>
|
||||
<version>1.75.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
<version>1.70.0</version>
|
||||
<version>1.75.0</version>
|
||||
</dependency>
|
||||
<dependency> <!-- necessary for Java 9+ -->
|
||||
<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:
|
||||
```gradle
|
||||
runtimeOnly 'io.grpc:grpc-netty-shaded:1.70.0'
|
||||
implementation 'io.grpc:grpc-protobuf:1.70.0'
|
||||
implementation 'io.grpc:grpc-stub:1.70.0'
|
||||
runtimeOnly 'io.grpc:grpc-netty-shaded:1.75.0'
|
||||
implementation 'io.grpc:grpc-protobuf:1.75.0'
|
||||
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||
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
|
||||
`grpc-protobuf-lite` instead of `grpc-protobuf`:
|
||||
```gradle
|
||||
implementation 'io.grpc:grpc-okhttp:1.70.0'
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.70.0'
|
||||
implementation 'io.grpc:grpc-stub:1.70.0'
|
||||
implementation 'io.grpc:grpc-okhttp:1.75.0'
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0'
|
||||
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||
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).
|
||||
|
||||
[the JARs]:
|
||||
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.70.0
|
||||
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.75.0
|
||||
|
||||
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
|
||||
--------------
|
||||
|
@ -131,7 +131,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use
|
|||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.70.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.75.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -152,7 +152,7 @@ For non-Android protobuf-based codegen integrated with the Gradle build system,
|
|||
you can use [protobuf-gradle-plugin][]:
|
||||
```gradle
|
||||
plugins {
|
||||
id 'com.google.protobuf' version '0.9.4'
|
||||
id 'com.google.protobuf' version '0.9.5'
|
||||
}
|
||||
|
||||
protobuf {
|
||||
|
@ -161,7 +161,7 @@ protobuf {
|
|||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.70.0'
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -185,7 +185,7 @@ use protobuf-gradle-plugin but specify the 'lite' options:
|
|||
|
||||
```gradle
|
||||
plugins {
|
||||
id 'com.google.protobuf' version '0.9.4'
|
||||
id 'com.google.protobuf' version '0.9.5'
|
||||
}
|
||||
|
||||
protobuf {
|
||||
|
@ -194,7 +194,7 @@ protobuf {
|
|||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.70.0'
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
|
|
@ -160,7 +160,7 @@ Tagging the Release
|
|||
repository can then be `released`, which will begin the process of pushing
|
||||
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
|
||||
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
|
||||
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.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.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.)_
|
||||
|
||||
|
|
|
@ -22,20 +22,19 @@ load("//:repositories.bzl", "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_deps")
|
||||
|
||||
protobuf_deps()
|
||||
|
||||
load("@envoy_api//bazel:repositories.bzl", "api_dependencies")
|
||||
|
||||
api_dependencies()
|
||||
|
||||
load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
|
||||
|
||||
switched_rules_by_language(
|
||||
name = "com_google_googleapis_imports",
|
||||
java = True,
|
||||
)
|
||||
|
||||
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
|
||||
public final class HandshakerServiceGrpc {
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import io.grpc.Channel;
|
|||
import io.grpc.ClientCall;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.SharedResourceHolder.Resource;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
|
@ -45,6 +46,9 @@ final class HandshakerServiceChannel {
|
|||
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 final String target;
|
||||
|
||||
|
@ -57,12 +61,16 @@ final class HandshakerServiceChannel {
|
|||
/* Use its own event loop thread pool to avoid blocking. */
|
||||
EventLoopGroup eventGroup =
|
||||
new NioEventLoopGroup(1, new DefaultThreadFactory("handshaker pool", true));
|
||||
ManagedChannel channel = NettyChannelBuilder.forTarget(target)
|
||||
NettyChannelBuilder channelBuilder =
|
||||
NettyChannelBuilder.forTarget(target)
|
||||
.channelType(NioSocketChannel.class, InetSocketAddress.class)
|
||||
.directExecutor()
|
||||
.eventLoopGroup(eventGroup)
|
||||
.usePlaintext()
|
||||
.build();
|
||||
.usePlaintext();
|
||||
if (EXPERIMENTAL_ALTS_HANDSHAKER_KEEPALIVE_PARAMS) {
|
||||
channelBuilder.keepAliveTime(10, TimeUnit.MINUTES).keepAliveTimeout(10, TimeUnit.SECONDS);
|
||||
}
|
||||
ManagedChannel channel = channelBuilder.build();
|
||||
return new EventLoopHoldingChannel(channel, eventGroup);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ repositories {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace 'io.grpc.android.integrationtest'
|
||||
namespace = 'io.grpc.android.integrationtest'
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
|
@ -41,7 +41,7 @@ android {
|
|||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
multiDexEnabled = true
|
||||
}
|
||||
buildTypes {
|
||||
debug { minifyEnabled false }
|
||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* A service used to obtain stats for verifying LB behavior.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class LoadBalancerStatsServiceGrpc {
|
||||
|
||||
|
@ -245,8 +242,8 @@ public final class LoadBalancerStatsServiceGrpc {
|
|||
* Gets the backend distribution for RPCs sent by a test client.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getGetClientStatsMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -255,8 +252,8 @@ public final class LoadBalancerStatsServiceGrpc {
|
|||
* Gets the accumulated stats for RPCs sent by a test client.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
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
|
||||
public final class MetricsServiceGrpc {
|
||||
|
||||
|
@ -245,8 +242,8 @@ public final class MetricsServiceGrpc {
|
|||
* Returns the value of one gauge
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getGetGaugeMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* A service used to control reconnect server.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
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) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.Messages.ReconnectParams request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getStartMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getStopMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* performance with various types of payload.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class TestServiceGrpc {
|
||||
|
||||
|
@ -576,8 +573,8 @@ public final class TestServiceGrpc {
|
|||
* One empty request followed by one empty response.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getEmptyCallMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -586,8 +583,8 @@ public final class TestServiceGrpc {
|
|||
* One request followed by one response.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -598,8 +595,8 @@ public final class TestServiceGrpc {
|
|||
* satisfy subsequent requests.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -664,8 +661,8 @@ public final class TestServiceGrpc {
|
|||
* to test the behavior when clients call unimplemented methods.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* that case.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class UnimplementedServiceGrpc {
|
||||
|
||||
|
@ -199,8 +196,8 @@ public final class UnimplementedServiceGrpc {
|
|||
* A call that no server should implement
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
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.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class XdsUpdateClientConfigureServiceGrpc {
|
||||
|
||||
|
@ -194,8 +191,8 @@ public final class XdsUpdateClientConfigureServiceGrpc {
|
|||
* Update the tes client's configuration.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
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.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
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) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty setServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getSetServingMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getSetNotServingMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* A service used to obtain stats for verifying LB behavior.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class LoadBalancerStatsServiceGrpc {
|
||||
|
||||
|
@ -245,8 +242,8 @@ public final class LoadBalancerStatsServiceGrpc {
|
|||
* Gets the backend distribution for RPCs sent by a test client.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getGetClientStatsMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -255,8 +252,8 @@ public final class LoadBalancerStatsServiceGrpc {
|
|||
* Gets the accumulated stats for RPCs sent by a test client.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
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
|
||||
public final class MetricsServiceGrpc {
|
||||
|
||||
|
@ -245,8 +242,8 @@ public final class MetricsServiceGrpc {
|
|||
* Returns the value of one gauge
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getGetGaugeMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* A service used to control reconnect server.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
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) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.Messages.ReconnectParams request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getStartMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getStopMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* performance with various types of payload.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class TestServiceGrpc {
|
||||
|
||||
|
@ -576,8 +573,8 @@ public final class TestServiceGrpc {
|
|||
* One empty request followed by one empty response.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getEmptyCallMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -586,8 +583,8 @@ public final class TestServiceGrpc {
|
|||
* One request followed by one response.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -598,8 +595,8 @@ public final class TestServiceGrpc {
|
|||
* satisfy subsequent requests.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -664,8 +661,8 @@ public final class TestServiceGrpc {
|
|||
* to test the behavior when clients call unimplemented methods.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,6 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* that case.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class UnimplementedServiceGrpc {
|
||||
|
||||
|
@ -199,8 +196,8 @@ public final class UnimplementedServiceGrpc {
|
|||
* A call that no server should implement
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
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.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class XdsUpdateClientConfigureServiceGrpc {
|
||||
|
||||
|
@ -194,8 +191,8 @@ public final class XdsUpdateClientConfigureServiceGrpc {
|
|||
* Update the tes client's configuration.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
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.
|
||||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler",
|
||||
comments = "Source: grpc/testing/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
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) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty setServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getSetServingMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getSetNotServingMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,20 +7,20 @@ plugins {
|
|||
description = 'gRPC: Android'
|
||||
|
||||
android {
|
||||
namespace 'io.grpc.android'
|
||||
namespace = 'io.grpc.android'
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
compileSdkVersion 34
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
lintOptions { abortOnError true }
|
||||
lintOptions { abortOnError = true }
|
||||
publishing {
|
||||
singleVariant('release') {
|
||||
withSourcesJar()
|
||||
|
|
|
@ -217,7 +217,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
|||
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
||||
unregisterRunnable =
|
||||
new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void run() {
|
||||
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
||||
|
@ -231,7 +230,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
|||
context.registerReceiver(networkReceiver, networkIntentFilter);
|
||||
unregisterRunnable =
|
||||
new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void run() {
|
||||
context.unregisterReceiver(networkReceiver);
|
||||
|
|
|
@ -47,6 +47,7 @@ dependencies {
|
|||
testImplementation project(':grpc-core')
|
||||
testImplementation project(':grpc-testing')
|
||||
testImplementation libraries.guava.testlib
|
||||
testImplementation libraries.truth
|
||||
|
||||
signature (libraries.signature.java) {
|
||||
artifact {
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
package io.grpc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -33,7 +35,7 @@ import java.util.concurrent.TimeUnit;
|
|||
* passed to the various components unambiguously.
|
||||
*/
|
||||
public final class Deadline implements Comparable<Deadline> {
|
||||
private static final SystemTicker SYSTEM_TICKER = new SystemTicker();
|
||||
private static final Ticker SYSTEM_TICKER = new SystemTicker();
|
||||
// nanoTime has a range of just under 300 years. Only allow up to 100 years in the past or future
|
||||
// to prevent wraparound as long as process runs for less than ~100 years.
|
||||
private static final long MAX_OFFSET = TimeUnit.DAYS.toNanos(100 * 365);
|
||||
|
@ -91,7 +93,7 @@ public final class Deadline implements Comparable<Deadline> {
|
|||
* @since 1.24.0
|
||||
*/
|
||||
public static Deadline after(long duration, TimeUnit units, Ticker ticker) {
|
||||
checkNotNull(units, "units");
|
||||
requireNonNull(units, "units");
|
||||
return new Deadline(ticker, units.toNanos(duration), true);
|
||||
}
|
||||
|
||||
|
@ -191,8 +193,8 @@ public final class Deadline implements Comparable<Deadline> {
|
|||
* @return {@link ScheduledFuture} which can be used to cancel execution of the task
|
||||
*/
|
||||
public ScheduledFuture<?> runOnExpiration(Runnable task, ScheduledExecutorService scheduler) {
|
||||
checkNotNull(task, "task");
|
||||
checkNotNull(scheduler, "scheduler");
|
||||
requireNonNull(task, "task");
|
||||
requireNonNull(scheduler, "scheduler");
|
||||
return scheduler.schedule(task, deadlineNanos - ticker.nanoTime(), TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
|
@ -225,37 +227,27 @@ public final class Deadline implements Comparable<Deadline> {
|
|||
@Override
|
||||
public int compareTo(Deadline that) {
|
||||
checkTicker(that);
|
||||
long diff = this.deadlineNanos - that.deadlineNanos;
|
||||
if (diff < 0) {
|
||||
return -1;
|
||||
} else if (diff > 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return Long.compare(this.deadlineNanos, that.deadlineNanos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.asList(this.ticker, this.deadlineNanos).hashCode();
|
||||
return Objects.hash(this.ticker, this.deadlineNanos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
public boolean equals(final Object object) {
|
||||
if (object == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Deadline)) {
|
||||
if (!(object instanceof Deadline)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Deadline other = (Deadline) o;
|
||||
if (this.ticker == null ? other.ticker != null : this.ticker != other.ticker) {
|
||||
final Deadline that = (Deadline) object;
|
||||
if (this.ticker == null ? that.ticker != null : this.ticker != that.ticker) {
|
||||
return false;
|
||||
}
|
||||
if (this.deadlineNanos != other.deadlineNanos) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return this.deadlineNanos == that.deadlineNanos;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -275,24 +267,17 @@ public final class Deadline implements Comparable<Deadline> {
|
|||
* @since 1.24.0
|
||||
*/
|
||||
public abstract static class Ticker {
|
||||
/** Returns the number of nanoseconds since this source's epoch. */
|
||||
/** Returns the number of nanoseconds elapsed since this ticker's reference point in time. */
|
||||
public abstract long nanoTime();
|
||||
}
|
||||
|
||||
private static class SystemTicker extends Ticker {
|
||||
private static final class SystemTicker extends Ticker {
|
||||
@Override
|
||||
public long nanoTime() {
|
||||
return System.nanoTime();
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T checkNotNull(T reference, Object errorMessage) {
|
||||
if (reference == null) {
|
||||
throw new NullPointerException(String.valueOf(errorMessage));
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
private void checkTicker(Deadline other) {
|
||||
if (ticker != other.ticker) {
|
||||
throw new AssertionError(
|
||||
|
|
|
@ -33,9 +33,9 @@ final class ConfiguratorRegistry {
|
|||
@GuardedBy("this")
|
||||
private boolean wasConfiguratorsSet;
|
||||
@GuardedBy("this")
|
||||
private boolean configFrozen;
|
||||
@GuardedBy("this")
|
||||
private List<Configurator> configurators = Collections.emptyList();
|
||||
@GuardedBy("this")
|
||||
private int configuratorsCallCountBeforeSet = 0;
|
||||
|
||||
ConfiguratorRegistry() {}
|
||||
|
||||
|
@ -56,11 +56,10 @@ final class ConfiguratorRegistry {
|
|||
* @throws IllegalStateException if this method is called more than once
|
||||
*/
|
||||
public synchronized void setConfigurators(List<? extends Configurator> configurators) {
|
||||
if (configFrozen) {
|
||||
if (wasConfiguratorsSet) {
|
||||
throw new IllegalStateException("Configurators are already set");
|
||||
}
|
||||
this.configurators = Collections.unmodifiableList(new ArrayList<>(configurators));
|
||||
configFrozen = true;
|
||||
wasConfiguratorsSet = true;
|
||||
}
|
||||
|
||||
|
@ -68,10 +67,20 @@ final class ConfiguratorRegistry {
|
|||
* Returns a list of the configurators in this registry.
|
||||
*/
|
||||
public synchronized List<Configurator> getConfigurators() {
|
||||
configFrozen = true;
|
||||
if (!wasConfiguratorsSet) {
|
||||
configuratorsCallCountBeforeSet++;
|
||||
}
|
||||
return configurators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of times getConfigurators() was called before
|
||||
* setConfigurators() was successfully invoked.
|
||||
*/
|
||||
public synchronized int getConfiguratorsCallCountBeforeSet() {
|
||||
return configuratorsCallCountBeforeSet;
|
||||
}
|
||||
|
||||
public synchronized boolean wasSetConfiguratorsCalled() {
|
||||
return wasConfiguratorsSet;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public abstract class InternalConfigSelector {
|
|||
= Attributes.Key.create("internal:io.grpc.config-selector");
|
||||
|
||||
// Use PickSubchannelArgs for SelectConfigArgs for now. May change over time.
|
||||
/** Selects the config for an PRC. */
|
||||
/** Selects the config for an RPC. */
|
||||
public abstract Result selectConfig(LoadBalancer.PickSubchannelArgs args);
|
||||
|
||||
public static final class Result {
|
||||
|
|
|
@ -48,4 +48,8 @@ public final class InternalConfiguratorRegistry {
|
|||
public static boolean wasSetConfiguratorsCalled() {
|
||||
return ConfiguratorRegistry.getDefaultRegistry().wasSetConfiguratorsCalled();
|
||||
}
|
||||
|
||||
public static int getConfiguratorsCallCountBeforeSet() {
|
||||
return ConfiguratorRegistry.getDefaultRegistry().getConfiguratorsCallCountBeforeSet();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -452,18 +452,6 @@ public abstract class LoadBalancer {
|
|||
* @since 1.3.0
|
||||
*/
|
||||
public abstract PickResult pickSubchannel(PickSubchannelArgs args);
|
||||
|
||||
/**
|
||||
* Tries to establish connections now so that the upcoming RPC may then just pick a ready
|
||||
* connection without having to connect first.
|
||||
*
|
||||
* <p>No-op if unsupported.
|
||||
*
|
||||
* @deprecated override {@link LoadBalancer#requestConnection} instead.
|
||||
* @since 1.11.0
|
||||
*/
|
||||
@Deprecated
|
||||
public void requestConnection() {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1201,6 +1189,10 @@ public abstract class LoadBalancer {
|
|||
* 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.
|
||||
*
|
||||
* <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
|
||||
* {@link ScheduledExecutorService}. {@link #getScheduledExecutorService} is provided for your
|
||||
* convenience.
|
||||
|
|
|
@ -22,6 +22,8 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
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.ByteStreams;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -32,8 +34,6 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -325,7 +325,7 @@ public final class Metadata {
|
|||
if (isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> ks = new HashSet<>(size);
|
||||
Set<String> ks = Sets.newHashSetWithExpectedSize(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
ks.add(new String(name(i), 0 /* hibyte */));
|
||||
}
|
||||
|
@ -526,7 +526,7 @@ public final class Metadata {
|
|||
public void merge(Metadata other, Set<Key<?>> keys) {
|
||||
Preconditions.checkNotNull(other, "other");
|
||||
// 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) {
|
||||
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
|
||||
* 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.
|
||||
* @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
|
||||
* 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
|
||||
* @since 1.21.0
|
||||
*/
|
||||
|
@ -255,9 +262,14 @@ public abstract class NameResolver {
|
|||
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
|
||||
*/
|
||||
public Status onResult2(ResolutionResult resolutionResult) {
|
||||
|
@ -275,6 +287,11 @@ public abstract class NameResolver {
|
|||
@Documented
|
||||
public @interface ResolutionResultAttr {}
|
||||
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11989")
|
||||
@ResolutionResultAttr
|
||||
public static final Attributes.Key<String> ATTR_BACKEND_SERVICE =
|
||||
Attributes.Key.create("io.grpc.NameResolver.ATTR_BACKEND_SERVICE");
|
||||
|
||||
/**
|
||||
* Information that a {@link Factory} uses to create a {@link NameResolver}.
|
||||
*
|
||||
|
@ -298,6 +315,7 @@ public abstract class NameResolver {
|
|||
@Nullable private final Executor executor;
|
||||
@Nullable private final String overrideAuthority;
|
||||
@Nullable private final MetricRecorder metricRecorder;
|
||||
@Nullable private final NameResolverRegistry nameResolverRegistry;
|
||||
@Nullable private final IdentityHashMap<Key<?>, Object> customArgs;
|
||||
|
||||
private Args(Builder builder) {
|
||||
|
@ -311,6 +329,7 @@ public abstract class NameResolver {
|
|||
this.executor = builder.executor;
|
||||
this.overrideAuthority = builder.overrideAuthority;
|
||||
this.metricRecorder = builder.metricRecorder;
|
||||
this.nameResolverRegistry = builder.nameResolverRegistry;
|
||||
this.customArgs = cloneCustomArgs(builder.customArgs);
|
||||
}
|
||||
|
||||
|
@ -442,6 +461,18 @@ public abstract class NameResolver {
|
|||
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
|
||||
public String toString() {
|
||||
|
@ -456,6 +487,7 @@ public abstract class NameResolver {
|
|||
.add("executor", executor)
|
||||
.add("overrideAuthority", overrideAuthority)
|
||||
.add("metricRecorder", metricRecorder)
|
||||
.add("nameResolverRegistry", nameResolverRegistry)
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
@ -475,6 +507,7 @@ public abstract class NameResolver {
|
|||
builder.setOffloadExecutor(executor);
|
||||
builder.setOverrideAuthority(overrideAuthority);
|
||||
builder.setMetricRecorder(metricRecorder);
|
||||
builder.setNameResolverRegistry(nameResolverRegistry);
|
||||
builder.customArgs = cloneCustomArgs(customArgs);
|
||||
return builder;
|
||||
}
|
||||
|
@ -503,6 +536,7 @@ public abstract class NameResolver {
|
|||
private Executor executor;
|
||||
private String overrideAuthority;
|
||||
private MetricRecorder metricRecorder;
|
||||
private NameResolverRegistry nameResolverRegistry;
|
||||
private IdentityHashMap<Key<?>, Object> customArgs;
|
||||
|
||||
Builder() {
|
||||
|
@ -609,6 +643,16 @@ public abstract class NameResolver {
|
|||
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}.
|
||||
*
|
||||
|
|
|
@ -166,6 +166,11 @@ public final class NameResolverRegistry {
|
|||
} catch (ClassNotFoundException 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,14 @@ public class StatusOr<T> {
|
|||
return status == null ? Status.OK : status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that StatusOr containing statuses, the equality comparision is delegated to
|
||||
* {@link Status#equals} which just does a reference equality check because equality on
|
||||
* Statuses is not well defined.
|
||||
* Instead, do comparison based on their Code with {@link Status#getCode}. The description and
|
||||
* cause of the Status are unlikely to be stable, and additional fields may be added to Status
|
||||
* in the future.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof StatusOr)) {
|
||||
|
|
|
@ -85,14 +85,12 @@ public class ConfiguratorRegistryTest {
|
|||
@Override
|
||||
public void run() {
|
||||
assertThat(ConfiguratorRegistry.getDefaultRegistry().getConfigurators()).isEmpty();
|
||||
|
||||
try {
|
||||
ConfiguratorRegistry.getDefaultRegistry()
|
||||
.setConfigurators(Arrays.asList(new NoopConfigurator()));
|
||||
fail("should have failed for invoking set call after get is already called");
|
||||
} catch (IllegalStateException e) {
|
||||
assertThat(e).hasMessageThat().isEqualTo("Configurators are already set");
|
||||
}
|
||||
NoopConfigurator noopConfigurator = new NoopConfigurator();
|
||||
ConfiguratorRegistry.getDefaultRegistry()
|
||||
.setConfigurators(Arrays.asList(noopConfigurator));
|
||||
assertThat(ConfiguratorRegistry.getDefaultRegistry().getConfigurators())
|
||||
.containsExactly(noopConfigurator);
|
||||
assertThat(InternalConfiguratorRegistry.getConfiguratorsCallCountBeforeSet()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.grpc;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
@ -24,6 +25,7 @@ import static org.junit.Assert.assertFalse;
|
|||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
@ -37,9 +39,7 @@ import java.io.InputStream;
|
|||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
|
@ -49,9 +49,6 @@ import org.junit.runners.JUnit4;
|
|||
@RunWith(JUnit4.class)
|
||||
public class MetadataTest {
|
||||
|
||||
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
|
||||
@Rule public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private static final Metadata.BinaryMarshaller<Fish> FISH_MARSHALLER =
|
||||
new Metadata.BinaryMarshaller<Fish>() {
|
||||
@Override
|
||||
|
@ -65,7 +62,7 @@ public class MetadataTest {
|
|||
}
|
||||
};
|
||||
|
||||
private static class FishStreamMarsaller implements Metadata.BinaryStreamMarshaller<Fish> {
|
||||
private static class FishStreamMarshaller implements Metadata.BinaryStreamMarshaller<Fish> {
|
||||
@Override
|
||||
public InputStream toStream(Fish fish) {
|
||||
return new ByteArrayInputStream(FISH_MARSHALLER.toBytes(fish));
|
||||
|
@ -82,7 +79,7 @@ public class MetadataTest {
|
|||
}
|
||||
|
||||
private static final Metadata.BinaryStreamMarshaller<Fish> FISH_STREAM_MARSHALLER =
|
||||
new FishStreamMarsaller();
|
||||
new FishStreamMarshaller();
|
||||
|
||||
/** A pattern commonly used to avoid unnecessary serialization of immutable objects. */
|
||||
private static final class FakeFishStream extends InputStream {
|
||||
|
@ -121,10 +118,9 @@ public class MetadataTest {
|
|||
|
||||
@Test
|
||||
public void noPseudoHeaders() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Invalid character");
|
||||
|
||||
Metadata.Key.of(":test-bin", FISH_MARSHALLER);
|
||||
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
|
||||
() -> Metadata.Key.of(":test-bin", FISH_MARSHALLER));
|
||||
assertThat(e).hasMessageThat().isEqualTo("Invalid character ':' in key name ':test-bin'");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -186,8 +182,7 @@ public class MetadataTest {
|
|||
Iterator<Fish> i = metadata.getAll(KEY).iterator();
|
||||
assertEquals(lance, i.next());
|
||||
|
||||
thrown.expect(UnsupportedOperationException.class);
|
||||
i.remove();
|
||||
assertThrows(UnsupportedOperationException.class, i::remove);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -271,17 +266,15 @@ public class MetadataTest {
|
|||
|
||||
@Test
|
||||
public void shortBinaryKeyName() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
|
||||
Metadata.Key.of("-bin", FISH_MARSHALLER);
|
||||
assertThrows(IllegalArgumentException.class, () -> Metadata.Key.of("-bin", FISH_MARSHALLER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidSuffixBinaryKeyName() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Binary header is named");
|
||||
|
||||
Metadata.Key.of("nonbinary", FISH_MARSHALLER);
|
||||
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
|
||||
() -> Metadata.Key.of("nonbinary", FISH_MARSHALLER));
|
||||
assertThat(e).hasMessageThat()
|
||||
.isEqualTo("Binary header is named nonbinary. It must end with -bin");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -415,7 +408,7 @@ public class MetadataTest {
|
|||
h.put(KEY_STREAMED, salmon);
|
||||
|
||||
// Get using a different marshaller instance.
|
||||
Fish fish = h.get(copyKey(KEY_STREAMED, new FishStreamMarsaller()));
|
||||
Fish fish = h.get(copyKey(KEY_STREAMED, new FishStreamMarshaller()));
|
||||
assertEquals(salmon, fish);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,7 @@ import static org.junit.Assert.assertTrue;
|
|||
import io.grpc.MethodDescriptor.Marshaller;
|
||||
import io.grpc.MethodDescriptor.MethodType;
|
||||
import io.grpc.testing.TestMethodDescriptors;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
|
@ -37,10 +35,6 @@ import org.junit.runners.JUnit4;
|
|||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class MethodDescriptorTest {
|
||||
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void createMethodDescriptor() {
|
||||
MethodDescriptor<String, String> descriptor = MethodDescriptor.<String, String>newBuilder()
|
||||
|
|
|
@ -19,6 +19,7 @@ package io.grpc;
|
|||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.AdditionalAnswers.delegatesTo;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
|
@ -40,7 +41,6 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
|
@ -55,10 +55,6 @@ public class ServerInterceptorsTest {
|
|||
@Rule
|
||||
public final MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private Marshaller<String> requestMarshaller;
|
||||
|
||||
|
@ -111,21 +107,21 @@ public class ServerInterceptorsTest {
|
|||
public void npeForNullServiceDefinition() {
|
||||
ServerServiceDefinition serviceDef = null;
|
||||
List<ServerInterceptor> interceptors = Arrays.asList();
|
||||
thrown.expect(NullPointerException.class);
|
||||
ServerInterceptors.intercept(serviceDef, interceptors);
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> ServerInterceptors.intercept(serviceDef, interceptors));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void npeForNullInterceptorList() {
|
||||
thrown.expect(NullPointerException.class);
|
||||
ServerInterceptors.intercept(serviceDefinition, (List<ServerInterceptor>) null);
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> ServerInterceptors.intercept(serviceDefinition, (List<ServerInterceptor>) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void npeForNullInterceptor() {
|
||||
List<ServerInterceptor> interceptors = Arrays.asList((ServerInterceptor) null);
|
||||
thrown.expect(NullPointerException.class);
|
||||
ServerInterceptors.intercept(serviceDefinition, interceptors);
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> ServerInterceptors.intercept(serviceDefinition, interceptors));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -18,14 +18,13 @@ package io.grpc;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
|
@ -52,9 +51,6 @@ public class ServerServiceDefinitionTest {
|
|||
= ServerMethodDefinition.create(method1, methodHandler1);
|
||||
private ServerMethodDefinition<String, Integer> methodDef2
|
||||
= ServerMethodDefinition.create(method2, methodHandler2);
|
||||
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void noMethods() {
|
||||
|
@ -91,9 +87,7 @@ public class ServerServiceDefinitionTest {
|
|||
ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
|
||||
ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
|
||||
.addMethod(method1, methodHandler1);
|
||||
thrown.expect(IllegalStateException.class);
|
||||
ssd.addMethod(diffMethod1, methodHandler2)
|
||||
.build();
|
||||
assertThrows(IllegalStateException.class, () -> ssd.addMethod(diffMethod1, methodHandler2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -101,8 +95,7 @@ public class ServerServiceDefinitionTest {
|
|||
ServiceDescriptor sd = new ServiceDescriptor(serviceName);
|
||||
ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
|
||||
.addMethod(methodDef1);
|
||||
thrown.expect(IllegalStateException.class);
|
||||
ssd.build();
|
||||
assertThrows(IllegalStateException.class, ssd::build);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -110,16 +103,14 @@ public class ServerServiceDefinitionTest {
|
|||
ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
|
||||
ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
|
||||
.addMethod(diffMethod1, methodHandler1);
|
||||
thrown.expect(IllegalStateException.class);
|
||||
ssd.build();
|
||||
assertThrows(IllegalStateException.class, ssd::build);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildMisaligned_missingMethod() {
|
||||
ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
|
||||
ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd);
|
||||
thrown.expect(IllegalStateException.class);
|
||||
ssd.build();
|
||||
assertThrows(IllegalStateException.class, ssd::build);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -16,17 +16,18 @@
|
|||
|
||||
package io.grpc;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.google.common.truth.StringSubject;
|
||||
import io.grpc.MethodDescriptor.MethodType;
|
||||
import io.grpc.testing.TestMethodDescriptors;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
|
@ -36,32 +37,27 @@ import org.junit.runners.JUnit4;
|
|||
@RunWith(JUnit4.class)
|
||||
public class ServiceDescriptorTest {
|
||||
|
||||
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void failsOnNullName() {
|
||||
thrown.expect(NullPointerException.class);
|
||||
thrown.expectMessage("name");
|
||||
|
||||
new ServiceDescriptor(null, Collections.<MethodDescriptor<?, ?>>emptyList());
|
||||
List<MethodDescriptor<?, ?>> methods = Collections.emptyList();
|
||||
NullPointerException e = assertThrows(NullPointerException.class,
|
||||
() -> new ServiceDescriptor(null, methods));
|
||||
assertThat(e).hasMessageThat().isEqualTo("name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failsOnNullMethods() {
|
||||
thrown.expect(NullPointerException.class);
|
||||
thrown.expectMessage("methods");
|
||||
|
||||
new ServiceDescriptor("name", (Collection<MethodDescriptor<?, ?>>) null);
|
||||
NullPointerException e = assertThrows(NullPointerException.class,
|
||||
() -> new ServiceDescriptor("name", (Collection<MethodDescriptor<?, ?>>) null));
|
||||
assertThat(e).hasMessageThat().isEqualTo("methods");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failsOnNullMethod() {
|
||||
thrown.expect(NullPointerException.class);
|
||||
thrown.expectMessage("method");
|
||||
|
||||
new ServiceDescriptor("name", Collections.<MethodDescriptor<?, ?>>singletonList(null));
|
||||
List<MethodDescriptor<?, ?>> methods = Collections.singletonList(null);
|
||||
NullPointerException e = assertThrows(NullPointerException.class,
|
||||
() -> new ServiceDescriptor("name", methods));
|
||||
assertThat(e).hasMessageThat().isEqualTo("method");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -69,15 +65,17 @@ public class ServiceDescriptorTest {
|
|||
List<MethodDescriptor<?, ?>> descriptors = Collections.<MethodDescriptor<?, ?>>singletonList(
|
||||
MethodDescriptor.<Void, Void>newBuilder()
|
||||
.setType(MethodType.UNARY)
|
||||
.setFullMethodName(MethodDescriptor.generateFullMethodName("wrongservice", "method"))
|
||||
.setFullMethodName(MethodDescriptor.generateFullMethodName("wrongService", "method"))
|
||||
.setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
|
||||
.setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
|
||||
.build());
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("service names");
|
||||
|
||||
new ServiceDescriptor("name", descriptors);
|
||||
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
|
||||
() -> new ServiceDescriptor("fooService", descriptors));
|
||||
StringSubject error = assertThat(e).hasMessageThat();
|
||||
error.contains("service names");
|
||||
error.contains("fooService");
|
||||
error.contains("wrongService");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -96,10 +94,9 @@ public class ServiceDescriptorTest {
|
|||
.setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
|
||||
.build());
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("duplicate");
|
||||
|
||||
new ServiceDescriptor("name", descriptors);
|
||||
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
|
||||
() -> new ServiceDescriptor("name", descriptors));
|
||||
assertThat(e).hasMessageThat().isEqualTo("duplicate name name/method");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import org.mockito.ArgumentMatcher;
|
||||
|
||||
/**
|
||||
* Mockito matcher for {@link Status}.
|
||||
*/
|
||||
public final class StatusMatcher implements ArgumentMatcher<Status> {
|
||||
public static StatusMatcher statusHasCode(ArgumentMatcher<Status.Code> codeMatcher) {
|
||||
return new StatusMatcher(codeMatcher, null);
|
||||
}
|
||||
|
||||
public static StatusMatcher statusHasCode(Status.Code code) {
|
||||
return statusHasCode(new EqualsMatcher<>(code));
|
||||
}
|
||||
|
||||
private final ArgumentMatcher<Status.Code> codeMatcher;
|
||||
private final ArgumentMatcher<String> descriptionMatcher;
|
||||
|
||||
private StatusMatcher(
|
||||
ArgumentMatcher<Status.Code> codeMatcher,
|
||||
ArgumentMatcher<String> descriptionMatcher) {
|
||||
this.codeMatcher = checkNotNull(codeMatcher, "codeMatcher");
|
||||
this.descriptionMatcher = descriptionMatcher;
|
||||
}
|
||||
|
||||
public StatusMatcher andDescription(ArgumentMatcher<String> descriptionMatcher) {
|
||||
checkState(this.descriptionMatcher == null, "Already has a description matcher");
|
||||
return new StatusMatcher(codeMatcher, descriptionMatcher);
|
||||
}
|
||||
|
||||
public StatusMatcher andDescription(String description) {
|
||||
return andDescription(new EqualsMatcher<>(description));
|
||||
}
|
||||
|
||||
public StatusMatcher andDescriptionContains(String substring) {
|
||||
return andDescription(new StringContainsMatcher(substring));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Status status) {
|
||||
return status != null
|
||||
&& codeMatcher.matches(status.getCode())
|
||||
&& (descriptionMatcher == null || descriptionMatcher.matches(status.getDescription()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{code=");
|
||||
sb.append(codeMatcher);
|
||||
if (descriptionMatcher != null) {
|
||||
sb.append(", description=");
|
||||
sb.append(descriptionMatcher);
|
||||
}
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// Use instead of lambda for better error message.
|
||||
static final class EqualsMatcher<T> implements ArgumentMatcher<T> {
|
||||
private final T obj;
|
||||
|
||||
EqualsMatcher(T obj) {
|
||||
this.obj = checkNotNull(obj, "obj");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object other) {
|
||||
return obj.equals(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static final class StringContainsMatcher implements ArgumentMatcher<String> {
|
||||
private final String needle;
|
||||
|
||||
StringContainsMatcher(String needle) {
|
||||
this.needle = checkNotNull(needle, "needle");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String haystack) {
|
||||
if (haystack == null) {
|
||||
return false;
|
||||
}
|
||||
return haystack.contains(needle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "contains " + needle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import org.mockito.ArgumentMatcher;
|
||||
|
||||
/**
|
||||
* Mockito matcher for {@link StatusOr}.
|
||||
*/
|
||||
public final class StatusOrMatcher<T> implements ArgumentMatcher<StatusOr<T>> {
|
||||
public static <T> StatusOrMatcher<T> hasValue(ArgumentMatcher<T> valueMatcher) {
|
||||
return new StatusOrMatcher<T>(checkNotNull(valueMatcher, "valueMatcher"), null);
|
||||
}
|
||||
|
||||
public static <T> StatusOrMatcher<T> hasStatus(ArgumentMatcher<Status> statusMatcher) {
|
||||
return new StatusOrMatcher<T>(null, checkNotNull(statusMatcher, "statusMatcher"));
|
||||
}
|
||||
|
||||
private final ArgumentMatcher<T> valueMatcher;
|
||||
private final ArgumentMatcher<Status> statusMatcher;
|
||||
|
||||
private StatusOrMatcher(ArgumentMatcher<T> valueMatcher, ArgumentMatcher<Status> statusMatcher) {
|
||||
this.valueMatcher = valueMatcher;
|
||||
this.statusMatcher = statusMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(StatusOr<T> statusOr) {
|
||||
if (statusOr == null) {
|
||||
return false;
|
||||
}
|
||||
if (statusOr.hasValue() != (valueMatcher != null)) {
|
||||
return false;
|
||||
}
|
||||
if (valueMatcher != null) {
|
||||
return valueMatcher.matches(statusOr.getValue());
|
||||
} else {
|
||||
return statusMatcher.matches(statusOr.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (valueMatcher != null) {
|
||||
return "{value=" + valueMatcher + "}";
|
||||
} else {
|
||||
return "{status=" + statusMatcher + "}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,10 +50,12 @@ import io.grpc.SecurityLevel;
|
|||
import io.grpc.Status;
|
||||
import io.grpc.internal.JsonParser;
|
||||
import io.grpc.testing.TestMethodDescriptors;
|
||||
import io.grpc.testing.TlsTesting;
|
||||
import io.grpc.util.CertificateUtils;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
@ -342,7 +344,10 @@ public class GoogleAuthLibraryCallCredentialsTest {
|
|||
|
||||
@Test
|
||||
public void serviceAccountToJwt() throws Exception {
|
||||
KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
|
||||
PrivateKey privateKey;
|
||||
try (InputStream server1Key = TlsTesting.loadCert("server1.key")) {
|
||||
privateKey = CertificateUtils.getPrivateKey(server1Key);
|
||||
}
|
||||
|
||||
HttpTransportFactory factory = Mockito.mock(HttpTransportFactory.class);
|
||||
Mockito.when(factory.create()).thenThrow(new AssertionError());
|
||||
|
@ -350,7 +355,7 @@ public class GoogleAuthLibraryCallCredentialsTest {
|
|||
ServiceAccountCredentials credentials =
|
||||
ServiceAccountCredentials.newBuilder()
|
||||
.setClientEmail("test-email@example.com")
|
||||
.setPrivateKey(pair.getPrivate())
|
||||
.setPrivateKey(privateKey)
|
||||
.setPrivateKeyId("test-private-key-id")
|
||||
.setHttpTransportFactory(factory)
|
||||
.build();
|
||||
|
@ -390,13 +395,16 @@ public class GoogleAuthLibraryCallCredentialsTest {
|
|||
|
||||
@Test
|
||||
public void jwtAccessCredentialsInRequestMetadata() throws Exception {
|
||||
KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
|
||||
PrivateKey privateKey;
|
||||
try (InputStream server1Key = TlsTesting.loadCert("server1.key")) {
|
||||
privateKey = CertificateUtils.getPrivateKey(server1Key);
|
||||
}
|
||||
|
||||
ServiceAccountCredentials credentials =
|
||||
ServiceAccountCredentials.newBuilder()
|
||||
.setClientId("test-client")
|
||||
.setClientEmail("test-email@example.com")
|
||||
.setPrivateKey(pair.getPrivate())
|
||||
.setPrivateKey(privateKey)
|
||||
.setPrivateKeyId("test-private-key-id")
|
||||
.setQuotaProjectId("test-quota-project-id")
|
||||
.build();
|
||||
|
|
|
@ -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
|
||||
public final class BenchmarkServiceGrpc {
|
||||
|
||||
|
@ -401,8 +398,8 @@ public final class BenchmarkServiceGrpc {
|
|||
* The server returns the client payload as-is.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.benchmarks.proto.Messages.SimpleResponse unaryCall(io.grpc.benchmarks.proto.Messages.SimpleRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.benchmarks.proto.Messages.SimpleResponse unaryCall(io.grpc.benchmarks.proto.Messages.SimpleRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
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
|
||||
public final class ReportQpsScenarioServiceGrpc {
|
||||
|
||||
|
@ -180,8 +177,8 @@ public final class ReportQpsScenarioServiceGrpc {
|
|||
* Report results of a QPS test benchmark scenario.
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.benchmarks.proto.Control.Void reportScenario(io.grpc.benchmarks.proto.Control.ScenarioResult request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.benchmarks.proto.Control.Void reportScenario(io.grpc.benchmarks.proto.Control.ScenarioResult request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
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
|
||||
public final class WorkerServiceGrpc {
|
||||
|
||||
|
@ -390,8 +387,8 @@ public final class WorkerServiceGrpc {
|
|||
* Just return the core count - unary call
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.benchmarks.proto.Control.CoreResponse coreCount(io.grpc.benchmarks.proto.Control.CoreRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.benchmarks.proto.Control.CoreResponse coreCount(io.grpc.benchmarks.proto.Control.CoreRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getCoreCountMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
|
@ -400,8 +397,8 @@ public final class WorkerServiceGrpc {
|
|||
* Quit this worker
|
||||
* </pre>
|
||||
*/
|
||||
public io.grpc.benchmarks.proto.Control.Void quitWorker(io.grpc.benchmarks.proto.Control.Void request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
public io.grpc.benchmarks.proto.Control.Void quitWorker(io.grpc.benchmarks.proto.Control.Void request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getQuitWorkerMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,28 +6,28 @@ plugins {
|
|||
description = 'gRPC BinderChannel'
|
||||
|
||||
android {
|
||||
namespace 'io.grpc.binder'
|
||||
namespace = 'io.grpc.binder'
|
||||
compileSdkVersion 34
|
||||
compileOptions {
|
||||
sourceCompatibility 1.8
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
multiDexEnabled = true
|
||||
}
|
||||
lintOptions { abortOnError false }
|
||||
lintOptions { abortOnError = false }
|
||||
publishing {
|
||||
singleVariant('release') {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
}
|
||||
testFixtures { enable true }
|
||||
testFixtures { enable = true }
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -72,6 +72,7 @@ dependencies {
|
|||
androidTestImplementation testFixtures(project(':grpc-core'))
|
||||
|
||||
testFixturesImplementation libraries.guava.testlib
|
||||
testFixturesImplementation testFixtures(project(':grpc-core'))
|
||||
}
|
||||
|
||||
import net.ltgt.gradle.errorprone.CheckSeverity
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="action1"/>
|
||||
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name="io.grpc.binder.HostServices$HostService2" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="action2"/>
|
||||
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
|
|
@ -23,6 +23,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
@ -39,7 +40,6 @@ import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
|||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.NameResolverRegistry;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCall.Listener;
|
||||
import io.grpc.ServerCallHandler;
|
||||
|
@ -49,7 +49,6 @@ import io.grpc.ServerServiceDefinition;
|
|||
import io.grpc.Status.Code;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.testing.FakeNameResolverProvider;
|
||||
import io.grpc.stub.ClientCalls;
|
||||
import io.grpc.stub.MetadataUtils;
|
||||
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 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 =
|
||||
ParcelableUtils.metadataKey("poison-bin", PoisonParcelable.CREATOR);
|
||||
|
||||
|
@ -99,7 +97,6 @@ public final class BinderChannelSmokeTest {
|
|||
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
|
||||
.build();
|
||||
|
||||
FakeNameResolverProvider fakeNameResolverProvider;
|
||||
ManagedChannel channel;
|
||||
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
|
||||
AtomicReference<PeerUid> clientUidCapture = new AtomicReference<>();
|
||||
|
@ -138,8 +135,6 @@ public final class BinderChannelSmokeTest {
|
|||
PeerUids.newPeerIdentifyingServerInterceptor());
|
||||
|
||||
AndroidComponentAddress serverAddress = HostServices.allocateService(appContext);
|
||||
fakeNameResolverProvider = new FakeNameResolverProvider(SERVER_TARGET_URI, serverAddress);
|
||||
NameResolverRegistry.getDefaultRegistry().register(fakeNameResolverProvider);
|
||||
HostServices.configureService(
|
||||
serverAddress,
|
||||
HostServices.serviceParamsBuilder()
|
||||
|
@ -166,7 +161,6 @@ public final class BinderChannelSmokeTest {
|
|||
@After
|
||||
public void tearDown() throws Exception {
|
||||
channel.shutdownNow();
|
||||
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider);
|
||||
HostServices.awaitServiceShutdown();
|
||||
}
|
||||
|
||||
|
@ -235,7 +229,11 @@ public final class BinderChannelSmokeTest {
|
|||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
|
@ -245,7 +243,10 @@ public final class BinderChannelSmokeTest {
|
|||
channel =
|
||||
BinderChannelBuilder.forAddress(
|
||||
AndroidComponentAddress.forBindIntent(
|
||||
new Intent().setAction("action1").setPackage(appContext.getPackageName())),
|
||||
new Intent()
|
||||
.setAction("action1")
|
||||
.setData(Uri.parse("scheme://authority/path"))
|
||||
.setPackage(appContext.getPackageName())),
|
||||
appContext)
|
||||
.build();
|
||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package io.grpc.binder.internal;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.DeadObjectException;
|
||||
|
@ -24,9 +25,8 @@ import android.os.Parcel;
|
|||
import android.os.RemoteException;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
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.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import com.google.protobuf.Empty;
|
||||
import io.grpc.CallOptions;
|
||||
|
@ -38,13 +38,13 @@ import io.grpc.ServerServiceDefinition;
|
|||
import io.grpc.Status;
|
||||
import io.grpc.Status.Code;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import io.grpc.binder.AsyncSecurityPolicy;
|
||||
import io.grpc.binder.BinderServerBuilder;
|
||||
import io.grpc.binder.HostServices;
|
||||
import io.grpc.binder.SecurityPolicy;
|
||||
import io.grpc.binder.internal.OneWayBinderProxies.BlackHoleOneWayBinderProxy;
|
||||
import io.grpc.binder.internal.OneWayBinderProxies.BlockingBinderDecorator;
|
||||
import io.grpc.binder.internal.OneWayBinderProxies.ThrowingOneWayBinderProxy;
|
||||
import io.grpc.binder.internal.SettableAsyncSecurityPolicy.AuthRequest;
|
||||
import io.grpc.internal.ClientStream;
|
||||
import io.grpc.internal.ClientStreamListener;
|
||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||
|
@ -63,7 +63,6 @@ import java.util.concurrent.ExecutorService;
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Nullable;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -101,7 +100,7 @@ public final class BinderClientTransportTest {
|
|||
.build();
|
||||
|
||||
AndroidComponentAddress serverAddress;
|
||||
BinderTransport.BinderClientTransport transport;
|
||||
BinderClientTransport transport;
|
||||
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
|
||||
|
||||
private final ObjectPool<ScheduledExecutorService> executorServicePool =
|
||||
|
@ -154,23 +153,32 @@ public final class BinderClientTransportTest {
|
|||
.setScheduledExecutorPool(executorServicePool)
|
||||
.setOffloadExecutorPool(offloadServicePool);
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
public BinderClientTransportBuilder setSecurityPolicy(SecurityPolicy securityPolicy) {
|
||||
factoryBuilder.setSecurityPolicy(securityPolicy);
|
||||
return this;
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
public BinderClientTransportBuilder setBinderDecorator(
|
||||
OneWayBinderProxy.Decorator binderDecorator) {
|
||||
factoryBuilder.setBinderDecorator(binderDecorator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
public BinderClientTransportBuilder setReadyTimeoutMillis(int timeoutMillis) {
|
||||
factoryBuilder.setReadyTimeoutMillis(timeoutMillis);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinderTransport.BinderClientTransport build() {
|
||||
@CanIgnoreReturnValue
|
||||
public BinderClientTransportBuilder setPreAuthorizeServer(boolean preAuthorizeServer) {
|
||||
factoryBuilder.setPreAuthorizeServers(preAuthorizeServer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinderClientTransport build() {
|
||||
return factoryBuilder
|
||||
.buildClientTransportFactory()
|
||||
.newClientTransport(serverAddress, new ClientTransportOptions(), null);
|
||||
|
@ -189,7 +197,7 @@ public final class BinderClientTransportTest {
|
|||
private static void shutdownAndTerminate(ExecutorService executorService)
|
||||
throws InterruptedException {
|
||||
executorService.shutdownNow();
|
||||
if (!executorService.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
||||
if (!executorService.awaitTermination(TIMEOUT_SECONDS, SECONDS)) {
|
||||
throw new AssertionError("executor failed to terminate promptly");
|
||||
}
|
||||
}
|
||||
|
@ -370,27 +378,58 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testBlackHoleSecurityPolicyConnectTimeout() throws Exception {
|
||||
public void testBlackHoleSecurityPolicyAuthTimeout() throws Exception {
|
||||
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||
transport =
|
||||
new BinderClientTransportBuilder()
|
||||
.setSecurityPolicy(blockingSecurityPolicy)
|
||||
.setSecurityPolicy(securityPolicy)
|
||||
.setPreAuthorizeServer(false)
|
||||
.setReadyTimeoutMillis(1_234)
|
||||
.build();
|
||||
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();
|
||||
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
|
||||
assertThat(transportStatus.getDescription()).contains("1234");
|
||||
transportListener.awaitTermination();
|
||||
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
|
||||
// If the transport gave up waiting on auth, it should cancel its request.
|
||||
assertThat(authRequest.isCancelled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncSecurityPolicyFailure() throws Exception {
|
||||
public void testBlackHoleSecurityPolicyPreAuthTimeout() throws Exception {
|
||||
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
|
||||
RuntimeException exception = new NullPointerException();
|
||||
securityPolicy.setAuthorizationException(exception);
|
||||
transport =
|
||||
new BinderClientTransportBuilder()
|
||||
.setSecurityPolicy(securityPolicy)
|
||||
.setPreAuthorizeServer(true)
|
||||
.setReadyTimeoutMillis(1_234)
|
||||
.build();
|
||||
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();
|
||||
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
|
||||
assertThat(transportStatus.getCause()).isEqualTo(exception);
|
||||
|
@ -398,19 +437,72 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncSecurityPolicySuccess() throws Exception {
|
||||
public void testAsyncSecurityPolicyPreAuthFailure() throws Exception {
|
||||
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
|
||||
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED);
|
||||
transport =
|
||||
new BinderClientTransportBuilder()
|
||||
.setPreAuthorizeServer(true)
|
||||
.setSecurityPolicy(securityPolicy)
|
||||
.build();
|
||||
RuntimeException exception = new NullPointerException();
|
||||
transport.start(transportListener).run();
|
||||
securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS).setResult(exception);
|
||||
Status transportStatus = transportListener.awaitShutdown();
|
||||
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
|
||||
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
|
||||
assertThat(transportStatus.getCause()).isEqualTo(exception);
|
||||
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(
|
||||
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
|
||||
throws Exception {
|
||||
BinderClientTransport transport, TestTransportListener transportListener) throws Exception {
|
||||
transport.start(transportListener).run();
|
||||
transportListener.awaitReady();
|
||||
}
|
||||
|
@ -429,7 +521,7 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
public Status awaitShutdown() throws Exception {
|
||||
return shutdownStatus.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
return shutdownStatus.get(TIMEOUT_SECONDS, SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -440,7 +532,7 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
public void awaitTermination() throws Exception {
|
||||
isTerminated.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
isTerminated.get(TIMEOUT_SECONDS, SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -451,7 +543,7 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
public void awaitReady() throws Exception {
|
||||
isReady.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
isReady.get(TIMEOUT_SECONDS, SECONDS);
|
||||
}
|
||||
|
||||
@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.setChannelLogger(transportLogger());
|
||||
|
||||
return new BinderTransport.BinderClientTransport(
|
||||
builder.buildClientTransportFactory(), addr, options);
|
||||
return new BinderClientTransport(builder.buildClientTransportFactory(), addr, options);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -58,7 +58,7 @@ public final class AndroidComponentAddress extends SocketAddress {
|
|||
@Nullable
|
||||
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(
|
||||
bindIntent.getComponent() != null || bindIntent.getPackage() != null,
|
||||
"'bindIntent' must be explicit. Specify either a package or ComponentName.");
|
||||
|
@ -250,7 +250,22 @@ public final class AndroidComponentAddress extends SocketAddress {
|
|||
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")
|
||||
public Builder setTargetUser(@Nullable UserHandle targetUser) {
|
||||
this.targetUser = targetUser;
|
||||
|
|
|
@ -18,6 +18,8 @@ package io.grpc.binder;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.EquivalentAddressGroup;
|
||||
import io.grpc.ExperimentalApi;
|
||||
import io.grpc.NameResolver;
|
||||
|
||||
|
@ -32,15 +34,42 @@ public final class ApiConstants {
|
|||
*/
|
||||
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.
|
||||
*
|
||||
* <p>{@link UserHandle} can't reasonably be encoded in a target URI string. Instead, all
|
||||
* {@link io.grpc.NameResolverProvider}s producing {@link AndroidComponentAddress}es should let
|
||||
* clients address servers in another Android user using this argument.
|
||||
* <p>{@link UserHandle} can't reasonably be encoded in a target URI string. Instead, all {@link
|
||||
* io.grpc.NameResolverProvider}s producing {@link AndroidComponentAddress}es should let clients
|
||||
* 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 =
|
||||
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.
|
||||
*/
|
||||
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}
|
||||
* 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
|
||||
* normally reserved for system apps. See {@link android.content.Context#bindServiceAsUser} for
|
||||
* details.
|
||||
* <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.
|
||||
*
|
||||
* @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
|
||||
|
@ -279,6 +279,35 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
|||
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
|
||||
public BinderChannelBuilder idleTimeout(long value, TimeUnit unit) {
|
||||
checkState(
|
||||
|
@ -292,6 +321,8 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
|||
public ManagedChannel build() {
|
||||
transportFactoryBuilder.setOffloadExecutorPool(
|
||||
managedChannelImplBuilder.getOffloadExecutorPool());
|
||||
setNameResolverArg(
|
||||
ApiConstants.SOURCE_ANDROID_CONTEXT, transportFactoryBuilder.getSourceContext());
|
||||
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
|
||||
* DevicePolicyManager}.
|
||||
*/
|
||||
@RequiresApi(18)
|
||||
public static io.grpc.binder.SecurityPolicy isDeviceOwner(Context applicationContext) {
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(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
|
||||
* DevicePolicyManager}.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public static SecurityPolicy isProfileOwner(Context applicationContext) {
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(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.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tracks which {@link BinderTransport.BinderServerTransport} are currently active and allows
|
||||
* invoking a {@link Runnable} only once all transports are terminated.
|
||||
* Tracks which {@link BinderServerTransport} are currently active and allows invoking a {@link
|
||||
* Runnable} only once all transports are terminated.
|
||||
*/
|
||||
final class ActiveTransportTracker implements ServerListener {
|
||||
private final ServerListener delegate;
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.IBinder;
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
|
||||
/** An interface for managing a {@code Binder} connection. */
|
||||
interface Bindable {
|
||||
|
@ -45,6 +47,19 @@ interface Bindable {
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -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 OneWayBinderProxy.Decorator binderDecorator;
|
||||
final long readyTimeoutMillis;
|
||||
final boolean preAuthorizeServers; // TODO(jdcormie): Default to true.
|
||||
|
||||
ScheduledExecutorService executorService;
|
||||
Executor offloadExecutor;
|
||||
|
@ -75,18 +76,19 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
inboundParcelablePolicy = checkNotNull(builder.inboundParcelablePolicy);
|
||||
binderDecorator = checkNotNull(builder.binderDecorator);
|
||||
readyTimeoutMillis = builder.readyTimeoutMillis;
|
||||
preAuthorizeServers = builder.preAuthorizeServers;
|
||||
|
||||
executorService = scheduledExecutorPool.getObject();
|
||||
offloadExecutor = offloadExecutorPool.getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinderTransport.BinderClientTransport newClientTransport(
|
||||
public BinderClientTransport newClientTransport(
|
||||
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
|
||||
if (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
|
||||
|
@ -128,6 +130,7 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
InboundParcelablePolicy inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT;
|
||||
OneWayBinderProxy.Decorator binderDecorator = OneWayBinderProxy.IDENTITY_DECORATOR;
|
||||
long readyTimeoutMillis = 60_000;
|
||||
boolean preAuthorizeServers;
|
||||
|
||||
@Override
|
||||
public BinderClientTransportFactory buildClientTransportFactory() {
|
||||
|
@ -139,6 +142,10 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
return this;
|
||||
}
|
||||
|
||||
public Context getSourceContext() {
|
||||
return sourceContext;
|
||||
}
|
||||
|
||||
public Builder setOffloadExecutorPool(ObjectPool<? extends Executor> offloadExecutorPool) {
|
||||
this.offloadExecutorPool = checkNotNull(offloadExecutorPool, "offloadExecutorPool");
|
||||
return this;
|
||||
|
@ -216,5 +223,11 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
this.readyTimeoutMillis = readyTimeoutMillis;
|
||||
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,
|
||||
checkNotNull(executor, "Not started?"));
|
||||
// Create a new transport and let our listener know about it.
|
||||
BinderTransport.BinderServerTransport transport =
|
||||
new BinderTransport.BinderServerTransport(
|
||||
BinderServerTransport transport =
|
||||
new BinderServerTransport(
|
||||
executorServicePool,
|
||||
attrsBuilder.build(),
|
||||
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.checkState;
|
||||
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.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.TransactionTooLargeException;
|
||||
import androidx.annotation.BinderThread;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Ticker;
|
||||
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.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.InternalChannelz.SocketStats;
|
||||
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.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.ServerStream;
|
||||
import io.grpc.internal.ServerTransport;
|
||||
import io.grpc.internal.ServerTransportListener;
|
||||
import io.grpc.internal.StatsTraceContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
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.Logger;
|
||||
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
|
||||
*/
|
||||
@ThreadSafe
|
||||
public abstract class BinderTransport
|
||||
implements LeakSafeOneWayBinder.TransactionHandler, IBinder.DeathRecipient {
|
||||
public abstract class BinderTransport implements IBinder.DeathRecipient {
|
||||
|
||||
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;
|
||||
|
||||
/** 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. */
|
||||
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. */
|
||||
protected enum TransportState {
|
||||
|
@ -210,12 +176,14 @@ public abstract class BinderTransport
|
|||
private final FlowController flowController;
|
||||
|
||||
/** 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. */
|
||||
// Only read/written on @BinderThread.
|
||||
private long acknowledgedIncomingBytes;
|
||||
|
||||
private BinderTransport(
|
||||
protected BinderTransport(
|
||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||
Attributes attributes,
|
||||
OneWayBinderProxy.Decorator binderDecorator,
|
||||
|
@ -225,10 +193,9 @@ public abstract class BinderTransport
|
|||
this.attributes = attributes;
|
||||
this.logId = logId;
|
||||
scheduledExecutorService = executorServicePool.getObject();
|
||||
incomingBinder = new LeakSafeOneWayBinder(this);
|
||||
incomingBinder = new LeakSafeOneWayBinder(this::handleTransaction);
|
||||
ongoingCalls = new ConcurrentHashMap<>();
|
||||
flowController = new FlowController(TRANSACTION_BYTES_WINDOW);
|
||||
numIncomingBytes = new AtomicLong();
|
||||
}
|
||||
|
||||
// Override in child class.
|
||||
|
@ -299,7 +266,10 @@ public abstract class BinderTransport
|
|||
|
||||
@Override
|
||||
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")
|
||||
|
@ -423,8 +393,9 @@ public abstract class BinderTransport
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean handleTransaction(int code, Parcel parcel) {
|
||||
@BinderThread
|
||||
@VisibleForTesting
|
||||
final boolean handleTransaction(int code, Parcel parcel) {
|
||||
try {
|
||||
return handleTransactionInternal(code, parcel);
|
||||
} catch (RuntimeException e) {
|
||||
|
@ -440,6 +411,7 @@ public abstract class BinderTransport
|
|||
}
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
private boolean handleTransactionInternal(int code, Parcel parcel) {
|
||||
if (code < FIRST_CALL_ID) {
|
||||
synchronized (this) {
|
||||
|
@ -483,11 +455,12 @@ public abstract class BinderTransport
|
|||
if (inbound != null) {
|
||||
inbound.handleTransaction(parcel);
|
||||
}
|
||||
long nib = numIncomingBytes.addAndGet(size);
|
||||
if ((nib - acknowledgedIncomingBytes) > TRANSACTION_BYTES_WINDOW_FORCE_ACK) {
|
||||
numIncomingBytes += size;
|
||||
if ((numIncomingBytes - acknowledgedIncomingBytes) > TRANSACTION_BYTES_WINDOW_FORCE_ACK) {
|
||||
synchronized (this) {
|
||||
sendAcknowledgeBytes(checkNotNull(outgoingBinder));
|
||||
sendAcknowledgeBytes(checkNotNull(outgoingBinder), numIncomingBytes);
|
||||
}
|
||||
acknowledgedIncomingBytes = numIncomingBytes;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -519,10 +492,8 @@ public abstract class BinderTransport
|
|||
protected void handlePingResponse(Parcel parcel) {}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void sendAcknowledgeBytes(OneWayBinderProxy iBinder) {
|
||||
private void sendAcknowledgeBytes(OneWayBinderProxy iBinder, long n) {
|
||||
// Send a transaction to acknowledge reception of incoming data.
|
||||
long n = numIncomingBytes.get();
|
||||
acknowledgedIncomingBytes = n;
|
||||
try (ParcelHolder parcel = ParcelHolder.obtain()) {
|
||||
parcel.get().writeLong(n);
|
||||
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) {
|
||||
switch (next) {
|
||||
case SETUP:
|
||||
|
|
|
@ -610,10 +610,9 @@ abstract class Inbound<L extends StreamListener> implements StreamListener.Messa
|
|||
// Server-side inbound transactions.
|
||||
static final class ServerInbound extends Inbound<ServerStreamListener> {
|
||||
|
||||
private final BinderTransport.BinderServerTransport serverTransport;
|
||||
private final BinderServerTransport serverTransport;
|
||||
|
||||
ServerInbound(
|
||||
BinderTransport.BinderServerTransport transport, Attributes attributes, int callId) {
|
||||
ServerInbound(BinderServerTransport transport, Attributes attributes, int callId) {
|
||||
super(transport, attributes, callId);
|
||||
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.IBinder;
|
||||
import android.os.Parcel;
|
||||
import androidx.annotation.BinderThread;
|
||||
import io.grpc.Internal;
|
||||
import java.util.logging.Level;
|
||||
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
|
||||
* result will not delivered to the caller of {@link IBinder#transact}
|
||||
*/
|
||||
@BinderThread
|
||||
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.checkState;
|
||||
import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.os.Parcel;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
|
@ -397,8 +396,7 @@ abstract class Outbound {
|
|||
@GuardedBy("this")
|
||||
void setDeadline(Deadline deadline) {
|
||||
headers.discardAll(TIMEOUT_KEY);
|
||||
long effectiveTimeoutNanos = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||
headers.put(TIMEOUT_KEY, effectiveTimeoutNanos);
|
||||
headers.put(TIMEOUT_KEY, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ final class PingTracker {
|
|||
private synchronized void fail(Status status) {
|
||||
if (!done) {
|
||||
done = true;
|
||||
executor.execute(() -> callback.onFailure(status.asException()));
|
||||
executor.execute(() -> callback.onFailure(status));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,14 +23,22 @@ import android.content.ComponentName;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
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.UserHandle;
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.binder.BinderChannelCredentials;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -85,6 +93,8 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
|||
private final Observer observer;
|
||||
private final Executor mainThreadExecutor;
|
||||
|
||||
private static volatile Method queryIntentServicesAsUserMethod;
|
||||
|
||||
@GuardedBy("this")
|
||||
private State state;
|
||||
|
||||
|
@ -183,18 +193,27 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
|||
bindResult = context.bindService(bindIntent, conn, flags);
|
||||
break;
|
||||
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;
|
||||
case DEVICE_POLICY_BIND_SEVICE_ADMIN:
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
bindResult =
|
||||
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
||||
channelCredentials.getDevicePolicyAdminComponentName(),
|
||||
bindIntent,
|
||||
conn,
|
||||
flags,
|
||||
targetUserHandle);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
bindResult =
|
||||
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
||||
channelCredentials.getDevicePolicyAdminComponentName(),
|
||||
bindIntent,
|
||||
conn,
|
||||
flags,
|
||||
targetUserHandle);
|
||||
} else {
|
||||
return Status.INTERNAL.withDescription(
|
||||
"Device policy admin binding requires Android R+");
|
||||
}
|
||||
break;
|
||||
}
|
||||
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
|
||||
private void clearReferences() {
|
||||
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,12 +22,13 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
|||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import androidx.lifecycle.LifecycleService;
|
||||
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.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.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
@ -42,90 +43,143 @@ import io.grpc.ServerMethodDefinition;
|
|||
import io.grpc.ServerServiceDefinition;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.binder.internal.MainThreadScheduledExecutorService;
|
||||
import io.grpc.protobuf.lite.ProtoLiteUtils;
|
||||
import io.grpc.stub.ClientCalls;
|
||||
import io.grpc.stub.ServerCalls;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.annotation.Nullable;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ServiceController;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.annotation.LooperMode.Mode;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
||||
public final class RobolectricBinderSecurityTest {
|
||||
|
||||
private static final String SERVICE_NAME = "fake_service";
|
||||
private static final String FULL_METHOD_NAME = "fake_service/fake_method";
|
||||
private final Application context = ApplicationProvider.getApplicationContext();
|
||||
private ServiceController<SomeService> controller;
|
||||
private SomeService service;
|
||||
private final ArrayBlockingQueue<SettableFuture<Status>> statusesToSet =
|
||||
new ArrayBlockingQueue<>(128);
|
||||
private ManagedChannel channel;
|
||||
private Server server;
|
||||
|
||||
@Parameter public boolean preAuthServersParam;
|
||||
|
||||
@Parameters(name = "preAuthServersParam={0}")
|
||||
public static ImmutableList<Boolean> data() {
|
||||
return ImmutableList.of(true, false);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
controller = Robolectric.buildService(SomeService.class);
|
||||
service = controller.create().get();
|
||||
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);
|
||||
|
||||
AndroidComponentAddress listenAddress = AndroidComponentAddress.forContext(service);
|
||||
ScheduledExecutorService executor = service.getExecutor();
|
||||
ServiceInfo serviceInfo = new ServiceInfo();
|
||||
serviceInfo.name = "SomeService";
|
||||
serviceInfo.packageName = serverAppInfo.packageName;
|
||||
serviceInfo.applicationInfo = serverAppInfo;
|
||||
shadowOf(context.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||
|
||||
AndroidComponentAddress listenAddress =
|
||||
AndroidComponentAddress.forRemoteComponent(serviceInfo.packageName, serviceInfo.name);
|
||||
|
||||
MethodDescriptor<Empty, Empty> methodDesc = getMethodDescriptor();
|
||||
ServerCallHandler<Empty, Empty> callHandler =
|
||||
ServerCalls.asyncUnaryCall(
|
||||
(req, respObserver) -> {
|
||||
respObserver.onNext(req);
|
||||
respObserver.onCompleted();
|
||||
});
|
||||
ServerMethodDefinition<Empty, Empty> methodDef =
|
||||
ServerMethodDefinition.create(methodDesc, callHandler);
|
||||
ServerServiceDefinition def =
|
||||
ServerServiceDefinition.builder(SERVICE_NAME).addMethod(methodDef).build();
|
||||
|
||||
IBinderReceiver binderReceiver = new IBinderReceiver();
|
||||
server =
|
||||
BinderServerBuilder.forAddress(listenAddress, binderReceiver)
|
||||
.addService(def)
|
||||
.securityPolicy(
|
||||
ServerSecurityPolicy.newBuilder()
|
||||
.servicePolicy(
|
||||
SERVICE_NAME,
|
||||
new AsyncSecurityPolicy() {
|
||||
@Override
|
||||
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
|
||||
SettableFuture<Status> status = SettableFuture.create();
|
||||
statusesToSet.add(status);
|
||||
return status;
|
||||
}
|
||||
})
|
||||
.build())
|
||||
.build();
|
||||
try {
|
||||
server.start();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
shadowOf(context)
|
||||
.setComponentNameAndServiceForBindServiceForIntent(
|
||||
listenAddress.asBindIntent(),
|
||||
listenAddress.getComponent(),
|
||||
checkNotNull(binderReceiver.get()));
|
||||
channel =
|
||||
BinderChannelBuilder.forAddress(listenAddress, context)
|
||||
.executor(executor)
|
||||
.scheduledExecutorService(executor)
|
||||
.offloadExecutor(executor)
|
||||
.preAuthorizeServers(preAuthServersParam)
|
||||
.build();
|
||||
idleLoopers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
channel.shutdownNow();
|
||||
controller.destroy();
|
||||
server.shutdownNow();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_failed_returnsFailureStatus() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
service.setSecurityPolicyStatusWhenReady(Status.ALREADY_EXISTS);
|
||||
idleLoopers();
|
||||
statusesToSet.take().set(Status.ALREADY_EXISTS);
|
||||
|
||||
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.ALREADY_EXISTS);
|
||||
assertThat(status.get().getCode()).isEqualTo(Status.Code.ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_failedFuture_failsWithCodeInternal() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
service.setSecurityPolicyFailed(new IllegalStateException("oops"));
|
||||
idleLoopers();
|
||||
statusesToSet.take().setException(new IllegalStateException("oops"));
|
||||
|
||||
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.INTERNAL);
|
||||
assertThat(status.get().getCode()).isEqualTo(Status.Code.INTERNAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_allowed_returnsOkStatus() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
service.setSecurityPolicyStatusWhenReady(Status.OK);
|
||||
idleLoopers();
|
||||
statusesToSet.take().set(Status.OK);
|
||||
|
||||
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.OK);
|
||||
assertThat(status.get().getCode()).isEqualTo(Status.Code.OK);
|
||||
}
|
||||
|
||||
private ListenableFuture<Status> makeCall() {
|
||||
ClientCall<Empty, Empty> call =
|
||||
channel.newCall(
|
||||
getMethodDescriptor(), CallOptions.DEFAULT.withExecutor(service.getExecutor()));
|
||||
ClientCall<Empty, Empty> call = channel.newCall(getMethodDescriptor(), CallOptions.DEFAULT);
|
||||
ListenableFuture<Empty> responseFuture =
|
||||
ClientCalls.futureUnaryCall(call, Empty.getDefaultInstance());
|
||||
|
||||
idleLoopers();
|
||||
|
||||
return Futures.catching(
|
||||
Futures.transform(responseFuture, unused -> Status.OK, directExecutor()),
|
||||
StatusRuntimeException.class,
|
||||
|
@ -133,10 +187,6 @@ public final class RobolectricBinderSecurityTest {
|
|||
directExecutor());
|
||||
}
|
||||
|
||||
private static void idleLoopers() {
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
}
|
||||
|
||||
private static MethodDescriptor<Empty, Empty> getMethodDescriptor() {
|
||||
MethodDescriptor.Marshaller<Empty> marshaller =
|
||||
ProtoLiteUtils.marshaller(Empty.getDefaultInstance());
|
||||
|
@ -147,106 +197,4 @@ public final class RobolectricBinderSecurityTest {
|
|||
.setSampledToLocalTracing(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static class SomeService extends LifecycleService {
|
||||
|
||||
private final IBinderReceiver binderReceiver = new IBinderReceiver();
|
||||
private final ArrayBlockingQueue<SettableFuture<Status>> statusesToSet =
|
||||
new ArrayBlockingQueue<>(128);
|
||||
private Server server;
|
||||
private final ScheduledExecutorService scheduledExecutorService =
|
||||
new MainThreadScheduledExecutorService();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
MethodDescriptor<Empty, Empty> methodDesc = getMethodDescriptor();
|
||||
ServerCallHandler<Empty, Empty> callHandler =
|
||||
ServerCalls.asyncUnaryCall(
|
||||
(req, respObserver) -> {
|
||||
respObserver.onNext(req);
|
||||
respObserver.onCompleted();
|
||||
});
|
||||
ServerMethodDefinition<Empty, Empty> methodDef =
|
||||
ServerMethodDefinition.create(methodDesc, callHandler);
|
||||
ServerServiceDefinition def =
|
||||
ServerServiceDefinition.builder(SERVICE_NAME).addMethod(methodDef).build();
|
||||
|
||||
server =
|
||||
BinderServerBuilder.forAddress(AndroidComponentAddress.forContext(this), binderReceiver)
|
||||
.addService(def)
|
||||
.securityPolicy(
|
||||
ServerSecurityPolicy.newBuilder()
|
||||
.servicePolicy(
|
||||
SERVICE_NAME,
|
||||
new AsyncSecurityPolicy() {
|
||||
@Override
|
||||
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
|
||||
return Futures.submitAsync(
|
||||
() -> {
|
||||
SettableFuture<Status> status = SettableFuture.create();
|
||||
statusesToSet.add(status);
|
||||
return status;
|
||||
},
|
||||
getExecutor());
|
||||
}
|
||||
})
|
||||
.build())
|
||||
.executor(getExecutor())
|
||||
.scheduledExecutorService(getExecutor())
|
||||
.build();
|
||||
try {
|
||||
server.start();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
Application context = ApplicationProvider.getApplicationContext();
|
||||
ComponentName componentName = new ComponentName(context, SomeService.class);
|
||||
shadowOf(context)
|
||||
.setComponentNameAndServiceForBindService(
|
||||
componentName, checkNotNull(binderReceiver.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link ScheduledExecutorService} under which all of the gRPC computations run. The
|
||||
* execution of any pending tasks on this executor can be triggered via {@link #idleLoopers()}.
|
||||
*/
|
||||
ScheduledExecutorService getExecutor() {
|
||||
return scheduledExecutorService;
|
||||
}
|
||||
|
||||
void setSecurityPolicyStatusWhenReady(Status status) {
|
||||
getNextEnqueuedStatus().set(status);
|
||||
}
|
||||
|
||||
void setSecurityPolicyFailed(Exception e) {
|
||||
getNextEnqueuedStatus().setException(e);
|
||||
}
|
||||
|
||||
private SettableFuture<Status> getNextEnqueuedStatus() {
|
||||
@Nullable SettableFuture<Status> future = statusesToSet.poll();
|
||||
while (future == null) {
|
||||
// Keep idling until the future is available.
|
||||
idleLoopers();
|
||||
future = statusesToSet.poll();
|
||||
}
|
||||
return checkNotNull(future);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
super.onBind(intent);
|
||||
return checkNotNull(binderReceiver.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
server.shutdownNow();
|
||||
}
|
||||
|
||||
/** A future representing a task submitted to a {@link Handler}. */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -357,7 +357,7 @@ public final class SecurityPoliciesTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 21)
|
||||
@Config(sdk = Config.OLDEST_SDK)
|
||||
public void testIsProfileOwner_succeedsForProfileOwner() throws Exception {
|
||||
PackageInfo info =
|
||||
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
||||
|
@ -371,7 +371,7 @@ public final class SecurityPoliciesTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 21)
|
||||
@Config(sdk = Config.OLDEST_SDK)
|
||||
public void testIsProfileOwner_failsForNotProfileOwner() throws Exception {
|
||||
PackageInfo info =
|
||||
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
||||
|
@ -385,7 +385,7 @@ public final class SecurityPoliciesTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 21)
|
||||
@Config(sdk = Config.OLDEST_SDK)
|
||||
public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception {
|
||||
policy = SecurityPolicies.isProfileOwner(appContext);
|
||||
|
||||
|
@ -425,7 +425,7 @@ public final class SecurityPoliciesTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 21)
|
||||
@Config(sdk = Config.OLDEST_SDK)
|
||||
public void testIsProfileOwnerOnOrgOwned_failsForNotProfileOwner() throws Exception {
|
||||
PackageInfo info =
|
||||
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
|
||||
|
@ -439,7 +439,7 @@ public final class SecurityPoliciesTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 21)
|
||||
@Config(sdk = Config.OLDEST_SDK)
|
||||
public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception {
|
||||
policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
|
@ -29,11 +28,9 @@ import android.os.Looper;
|
|||
import android.os.Parcel;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.internal.FixedObjectPool;
|
||||
import io.grpc.internal.ServerStream;
|
||||
import io.grpc.internal.ServerTransportListener;
|
||||
import io.grpc.internal.MockServerTransportListener;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -55,21 +52,22 @@ public final class BinderServerTransportTest {
|
|||
@Rule public MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
private final ScheduledExecutorService executorService = new MainThreadScheduledExecutorService();
|
||||
private final TestTransportListener transportListener = new TestTransportListener();
|
||||
private MockServerTransportListener transportListener;
|
||||
|
||||
@Mock IBinder mockBinder;
|
||||
|
||||
BinderTransport.BinderServerTransport transport;
|
||||
BinderServerTransport transport;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
transport =
|
||||
new BinderTransport.BinderServerTransport(
|
||||
new BinderServerTransport(
|
||||
new FixedObjectPool<>(executorService),
|
||||
Attributes.EMPTY,
|
||||
ImmutableList.of(),
|
||||
OneWayBinderProxy.IDENTITY_DECORATOR,
|
||||
mockBinder);
|
||||
transportListener = new MockServerTransportListener(transport);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -82,34 +80,6 @@ public final class BinderServerTransportTest {
|
|||
transport.shutdownNow(Status.UNKNOWN.withDescription("reasons"));
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
assertThat(transportListener.terminated).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;
|
||||
}
|
||||
assertThat(transportListener.isTerminated()).isTrue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -96,7 +96,7 @@ public final class PingTrackerTest {
|
|||
private int numCallbacks;
|
||||
private boolean success;
|
||||
private boolean failure;
|
||||
private Throwable failureException;
|
||||
private Status failureStatus;
|
||||
private long roundtripTimeNanos;
|
||||
|
||||
@Override
|
||||
|
@ -107,10 +107,10 @@ public final class PingTrackerTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onFailure(Throwable failureException) {
|
||||
public synchronized void onFailure(Status failureStatus) {
|
||||
numCallbacks += 1;
|
||||
failure = true;
|
||||
this.failureException = failureException;
|
||||
this.failureStatus = failureStatus;
|
||||
}
|
||||
|
||||
public void assertNotCalled() {
|
||||
|
@ -130,13 +130,13 @@ public final class PingTrackerTest {
|
|||
public void assertFailure(Status status) {
|
||||
assertThat(numCallbacks).isEqualTo(1);
|
||||
assertThat(failure).isTrue();
|
||||
assertThat(((StatusException) failureException).getStatus()).isSameInstanceAs(status);
|
||||
assertThat(failureStatus).isSameInstanceAs(status);
|
||||
}
|
||||
|
||||
public void assertFailure(Status.Code statusCode) {
|
||||
assertThat(numCallbacks).isEqualTo(1);
|
||||
assertThat(failure).isTrue();
|
||||
assertThat(((StatusException) failureException).getStatus().getCode()).isEqualTo(statusCode);
|
||||
assertThat(failureStatus.getCode()).isEqualTo(statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
* 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.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 android.app.Application;
|
||||
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.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.Status;
|
||||
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.ClientTransportFactory.ClientTransportOptions;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.InternalServer;
|
||||
import io.grpc.internal.ManagedClientTransport;
|
||||
import io.grpc.internal.ObjectPool;
|
||||
import io.grpc.internal.SharedResourcePool;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
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.Mode;
|
||||
import org.robolectric.shadows.ShadowBinder;
|
||||
|
||||
/**
|
||||
* All of the AbstractTransportTest cases applied to {@link BinderTransport} running in a
|
||||
* Robolectric environment.
|
||||
*
|
||||
* <p>Runs much faster than BinderTransportTest and doesn't require an Android device/emulator.
|
||||
* Somewhat less realistic but allows simulating behavior that would be difficult or impossible with
|
||||
* real Android.
|
||||
*
|
||||
* <p>NB: Unlike most robolectric tests, we run in {@link LooperMode.Mode#INSTRUMENTATION_TEST},
|
||||
* 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.
|
||||
*/
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
||||
public final class RobolectricBinderTransportTest extends AbstractTransportTest {
|
||||
|
||||
private final Application application = ApplicationProvider.getApplicationContext();
|
||||
private final ObjectPool<ScheduledExecutorService> executorServicePool =
|
||||
SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
|
||||
private final ObjectPool<Executor> offloadExecutorPool =
|
||||
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
|
||||
private final ObjectPool<Executor> serverExecutorPool =
|
||||
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
|
||||
|
||||
@Rule public MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@Mock AsyncSecurityPolicy mockClientSecurityPolicy;
|
||||
|
||||
ApplicationInfo serverAppInfo;
|
||||
PackageInfo serverPkgInfo;
|
||||
ServiceInfo serviceInfo;
|
||||
|
||||
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
|
||||
protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
|
||||
AndroidComponentAddress listenAddr =
|
||||
AndroidComponentAddress.forBindIntent(
|
||||
new Intent()
|
||||
.setClassName(serviceInfo.packageName, serviceInfo.name)
|
||||
.setAction("io.grpc.action.BIND." + nextServerAddress++));
|
||||
|
||||
BinderServer binderServer =
|
||||
new BinderServer.Builder()
|
||||
.setListenAddress(listenAddr)
|
||||
.setExecutorPool(serverExecutorPool)
|
||||
.setExecutorServicePool(executorServicePool)
|
||||
.setStreamTracerFactories(streamTracerFactories)
|
||||
.build();
|
||||
|
||||
shadowOf(application.getPackageManager()).addServiceIfNotPresent(listenAddr.getComponent());
|
||||
shadowOf(application)
|
||||
.setComponentNameAndServiceForBindServiceForIntent(
|
||||
listenAddr.asBindIntent(), listenAddr.getComponent(), binderServer.getHostBinder());
|
||||
return binderServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InternalServer newServer(
|
||||
int port, List<ServerStreamTracer.Factory> streamTracerFactories) {
|
||||
if (port > 0) {
|
||||
// TODO: TCP ports have no place in an *abstract* transport test. Replace with SocketAddress.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
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
|
||||
protected ManagedClientTransport newClientTransport(InternalServer server) {
|
||||
ClientTransportOptions options = new ClientTransportOptions();
|
||||
options.setEagAttributes(eagAttrs());
|
||||
options.setChannelLogger(transportLogger());
|
||||
|
||||
return newClientTransportBuilder()
|
||||
.setServerAddress(server.getListenSocketAddress())
|
||||
.setOptions(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String testAuthority(InternalServer server) {
|
||||
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
|
||||
@Ignore("See BinderTransportTest#socketStats.")
|
||||
@Override
|
||||
public void socketStats() {}
|
||||
|
||||
@Test
|
||||
@Ignore("See BinderTransportTest#flowControlPushBack")
|
||||
@Override
|
||||
public void flowControlPushBack() {}
|
||||
|
||||
@Test
|
||||
@Ignore("See BinderTransportTest#serverAlreadyListening")
|
||||
@Override
|
||||
public void serverAlreadyListening() {}
|
||||
}
|
|
@ -19,6 +19,7 @@ package io.grpc.binder.internal;
|
|||
import static android.content.Context.BIND_AUTO_CREATE;
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
|
@ -27,6 +28,8 @@ import android.app.admin.DevicePolicyManager;
|
|||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.UserHandle;
|
||||
|
@ -34,6 +37,7 @@ import androidx.core.content.ContextCompat;
|
|||
import androidx.test.core.app.ApplicationProvider;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.Status.Code;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.binder.BinderChannelCredentials;
|
||||
import io.grpc.binder.internal.Bindable.Observer;
|
||||
import java.util.Arrays;
|
||||
|
@ -59,6 +63,7 @@ public final class ServiceBindingTest {
|
|||
|
||||
private Application appContext;
|
||||
private ComponentName serviceComponent;
|
||||
private ServiceInfo serviceInfo = new ServiceInfo();
|
||||
private ShadowApplication shadowApplication;
|
||||
private TestObserver observer;
|
||||
private ServiceBinding binding;
|
||||
|
@ -67,13 +72,17 @@ public final class ServiceBindingTest {
|
|||
public void setUp() {
|
||||
appContext = ApplicationProvider.getApplicationContext();
|
||||
serviceComponent = new ComponentName("DUMMY", "SERVICE");
|
||||
serviceInfo.packageName = serviceComponent.getPackageName();
|
||||
serviceInfo.name = serviceComponent.getClassName();
|
||||
observer = new TestObserver();
|
||||
|
||||
shadowApplication = shadowOf(appContext);
|
||||
shadowApplication.setComponentNameAndServiceForBindService(serviceComponent, mockBinder);
|
||||
shadowOf(appContext.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||
|
||||
// Don't call onServiceDisconnected() upon unbindService(), just like the real Android doesn't.
|
||||
shadowApplication.setUnbindServiceCallsOnServiceDisconnected(false);
|
||||
shadowApplication.setBindServiceCallsOnServiceConnectedDirectly(false);
|
||||
|
||||
binding = newBuilder().build();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
@ -276,6 +285,93 @@ public final class ServiceBindingTest {
|
|||
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
|
||||
@Config(sdk = 30)
|
||||
public void testBindWithDeviceAdmin() throws Exception {
|
||||
|
@ -284,7 +380,7 @@ public final class ServiceBindingTest {
|
|||
allowBindDeviceAdminForUser(appContext, adminComponent, /* userId= */ 0);
|
||||
binding =
|
||||
newBuilder()
|
||||
.setTargetUserHandle(UserHandle.getUserHandleForUid(/* userId= */ 0))
|
||||
.setTargetUserHandle(UserHandle.getUserHandleForUid(/* uid= */ 0))
|
||||
.setTargetUserHandle(generateUserHandle(/* userId= */ 0))
|
||||
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
|
||||
.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();
|
||||
}
|
||||
}
|
||||
}
|
22
build.gradle
22
build.gradle
|
@ -21,11 +21,11 @@ subprojects {
|
|||
apply plugin: "net.ltgt.errorprone"
|
||||
|
||||
group = "io.grpc"
|
||||
version = "1.71.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
||||
version = "1.76.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
||||
|
||||
repositories {
|
||||
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 {
|
||||
mavenPom()
|
||||
ignoreGradleMetadataRedirection()
|
||||
|
@ -151,7 +151,7 @@ subprojects {
|
|||
appendToProperty(
|
||||
it.options.errorprone.excludedPaths,
|
||||
".*/src/generated/[^/]+/java/.*" +
|
||||
"|.*/build/generated/source/proto/[^/]+/java/.*",
|
||||
"|.*/build/generated/sources/proto/[^/]+/java/.*",
|
||||
"|")
|
||||
}
|
||||
}
|
||||
|
@ -241,9 +241,9 @@ subprojects {
|
|||
tasks.named("test").configure {
|
||||
testLogging {
|
||||
exceptionFormat = 'full'
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
}
|
||||
maxHeapSize = '1500m'
|
||||
}
|
||||
|
@ -389,11 +389,11 @@ subprojects {
|
|||
url = new File(rootProject.repositoryDir).toURI()
|
||||
} else {
|
||||
String stagingUrl
|
||||
String baseUrl = "https://ossrh-staging-api.central.sonatype.com/service/local"
|
||||
if (rootProject.hasProperty('repositoryId')) {
|
||||
stagingUrl = 'https://oss.sonatype.org/service/local/staging/deployByRepositoryId/' +
|
||||
rootProject.repositoryId
|
||||
stagingUrl = "${baseUrl}/staging/deployByRepositoryId/" + rootProject.repositoryId
|
||||
} else {
|
||||
stagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
|
||||
stagingUrl = "${baseUrl}/staging/deploy/maven2/"
|
||||
}
|
||||
credentials {
|
||||
if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) {
|
||||
|
@ -402,7 +402,7 @@ subprojects {
|
|||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -410,7 +410,7 @@ subprojects {
|
|||
}
|
||||
|
||||
signing {
|
||||
required false
|
||||
required = false
|
||||
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" && \
|
||||
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
|
||||
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
|
||||
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++-powerpc64le-linux-gnu \
|
||||
openjdk-8-jdk \
|
||||
pkg-config \
|
||||
&& \
|
||||
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 \
|
||||
g++-s390x-linux-gnu \
|
||||
openjdk-8-jdk \
|
||||
pkg-config \
|
||||
&& \
|
||||
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
|
||||
|
||||
# Install gRPC and codegen for the Android interop app
|
||||
# (a composite gradle build can't find protoc-gen-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
|
||||
mkdir -p "${ANDROID_HOME}/cmdline-tools"
|
||||
curl -Ls -o cmdline.zip \
|
||||
|
@ -21,15 +13,12 @@ rm cmdline.zip
|
|||
mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest"
|
||||
(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
|
||||
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 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:assembleDebugAndroidTest
|
||||
|
|
|
@ -9,9 +9,6 @@ BASE_DIR="$(pwd)"
|
|||
|
||||
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)
|
||||
|
||||
cat <<EOF >> gradle.properties
|
||||
|
@ -30,10 +27,18 @@ unzip -qd "${ANDROID_HOME}/cmdline-tools" cmdline.zip
|
|||
rm cmdline.zip
|
||||
mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest"
|
||||
(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
|
||||
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
|
||||
sudo update-java-alternatives --set java-1.11.0-openjdk-amd64
|
||||
# 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
|
||||
git checkout HEAD^
|
||||
./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
|
||||
cd examples/android/helloworld/
|
||||
../../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"
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Location of the continuous shell script in repository.
|
||||
build_file: "grpc-java/buildscripts/kokoro/psm-interop-test-java.sh"
|
||||
timeout_mins: 120
|
||||
timeout_mins: 240
|
||||
|
||||
action {
|
||||
define_artifacts {
|
||||
|
|
|
@ -13,5 +13,5 @@ action {
|
|||
}
|
||||
env_vars {
|
||||
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'"
|
||||
|
||||
# Make protobuf discoverable by :grpc-compiler
|
||||
export LD_LIBRARY_PATH=/tmp/protobuf/lib
|
||||
export LDFLAGS=-L/tmp/protobuf/lib
|
||||
export CXXFLAGS="-I/tmp/protobuf/include"
|
||||
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 LIBRARY_PATH=/tmp/protobuf/lib
|
||||
|
||||
./gradlew grpc-compiler:clean $GRADLE_FLAGS
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Location of the continuous shell script in repository.
|
||||
build_file: "grpc-java/buildscripts/kokoro/windows.bat"
|
||||
timeout_mins: 45
|
||||
timeout_mins: 90
|
||||
|
||||
# We always build mvn artifacts.
|
||||
action {
|
||||
|
|
|
@ -15,19 +15,21 @@ set ESCWORKSPACE=%WORKSPACE:\=\\%
|
|||
|
||||
@rem Clear JAVA_HOME to prevent a different Java version from being used
|
||||
set JAVA_HOME=
|
||||
set PATH=C:\Program Files\OpenJDK\openjdk-11.0.12_7\bin;%PATH%
|
||||
|
||||
mkdir 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
|
||||
|
||||
cd "%WORKSPACE%"
|
||||
|
||||
SET TARGET_ARCH=x86_32
|
||||
SET FAIL_ON_WARNINGS=true
|
||||
SET VC_PROTOBUF_LIBS=%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\build\\Release
|
||||
SET VC_PROTOBUF_INCLUDE=%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\build\\include
|
||||
SET PROTOBUF_VER=22.5
|
||||
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_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 %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
|
||||
set JAVA_HOME=
|
||||
set PATH=C:\Program Files\OpenJDK\openjdk-11.0.12_7\bin;%PATH%
|
||||
|
||||
mkdir 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
|
||||
|
||||
cd "%WORKSPACE%"
|
||||
|
||||
SET TARGET_ARCH=x86_64
|
||||
SET FAIL_ON_WARNINGS=true
|
||||
SET VC_PROTOBUF_LIBS=%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\build\\Release
|
||||
SET VC_PROTOBUF_INCLUDE=%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\build\\include
|
||||
SET PROTOBUF_VER=22.5
|
||||
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_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 %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
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue