Compare commits

..

2 Commits

Author SHA1 Message Date
Kannan J 865c443256 Bump version to 1.71.0 2025-03-04 14:23:13 +00:00
Kannan J ac9d0d2d4d Update README etc to reference 1.71.0 2025-03-04 14:01:57 +00:00
469 changed files with 9502 additions and 17941 deletions

View File

@ -102,9 +102,6 @@ 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

View File

@ -33,6 +33,7 @@ java_library(
"//api",
"//protobuf",
"//stub",
"//stub:javax_annotation",
"@com_google_protobuf//:protobuf_java",
artifact("com.google.code.findbugs:jsr305"),
artifact("com.google.guava:guava"),
@ -46,6 +47,7 @@ java_library(
"//api",
"//protobuf-lite",
"//stub",
"//stub:javax_annotation",
artifact("com.google.code.findbugs:jsr305"),
artifact("com.google.guava:guava"),
],
@ -65,5 +67,6 @@ java_library(
visibility = ["//:__subpackages__"],
exports = [
artifact("com.google.auto.value:auto-value-annotations"),
artifact("org.apache.tomcat:annotations-api"), # @Generated for Java 9+
],
)

View File

@ -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 22.5 or later.
The codegen plugin is C++ code and requires protobuf 21.7 or later.
For Linux, Mac and MinGW:
```
$ PROTOBUF_VERSION=22.5
$ PROTOBUF_VERSION=21.7
$ 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

View File

@ -11,6 +11,8 @@ 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
@ -24,9 +26,7 @@ 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)

View File

@ -2,13 +2,13 @@ module(
name = "grpc-java",
compatibility_level = 0,
repo_name = "io_grpc_grpc_java",
version = "1.76.0-SNAPSHOT", # CURRENT_GRPC_VERSION
version = "1.71.0", # 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.59.2",
"com.google.api.grpc:proto-google-common-protos:2.51.0",
"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,44 +19,63 @@ 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.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-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-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.124.Final",
"io.netty:netty-transport-native-unix-common:4.1.124.Final",
"io.netty:netty-transport:4.1.124.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.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")
# 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 = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
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(
@ -183,3 +202,7 @@ 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)

View File

@ -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.75.0/examples) and the
[Android example](https://github.com/grpc/grpc-java/tree/v1.75.0/examples/android)
The [examples](https://github.com/grpc/grpc-java/tree/v1.71.0/examples) and the
[Android example](https://github.com/grpc/grpc-java/tree/v1.71.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.75.0</version>
<version>1.71.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.75.0</version>
<version>1.71.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.75.0</version>
<version>1.71.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.75.0'
implementation 'io.grpc:grpc-protobuf:1.75.0'
implementation 'io.grpc:grpc-stub:1.75.0'
runtimeOnly 'io.grpc:grpc-netty-shaded:1.71.0'
implementation 'io.grpc:grpc-protobuf:1.71.0'
implementation 'io.grpc:grpc-stub:1.71.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.75.0'
implementation 'io.grpc:grpc-protobuf-lite:1.75.0'
implementation 'io.grpc:grpc-stub:1.75.0'
implementation 'io.grpc:grpc-okhttp:1.71.0'
implementation 'io.grpc:grpc-protobuf-lite:1.71.0'
implementation 'io.grpc:grpc-stub:1.71.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.75.0
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.71.0
Development snapshots are available in [Sonatypes's snapshot
repository](https://central.sonatype.com/repository/maven-snapshots/).
repository](https://oss.sonatype.org/content/repositories/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.75.0:exe:${os.detected.classifier}</pluginArtifact>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.71.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.5'
id 'com.google.protobuf' version '0.9.4'
}
protobuf {
@ -161,7 +161,7 @@ protobuf {
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
artifact = 'io.grpc:protoc-gen-grpc-java:1.71.0'
}
}
generateProtoTasks {
@ -185,7 +185,7 @@ use protobuf-gradle-plugin but specify the 'lite' options:
```gradle
plugins {
id 'com.google.protobuf' version '0.9.5'
id 'com.google.protobuf' version '0.9.4'
}
protobuf {
@ -194,7 +194,7 @@ protobuf {
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
artifact = 'io.grpc:protoc-gen-grpc-java:1.71.0'
}
}
generateProtoTasks {

View File

@ -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/publish/publish-portal-ossrh-staging-api/#deploying).
Central on the [OSSRH site](https://central.sonatype.org/pages/releasing-the-deployment.html).
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

View File

@ -400,8 +400,7 @@ 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-1.74.x | 4.1.110.Final | 2.0.70.Final
1.75.x- | 4.1.124.Final | 2.0.72.Final
1.71.x- | 4.1.110.Final | 2.0.70.Final
_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_

View File

@ -22,19 +22,20 @@ 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(

View File

@ -4,6 +4,9 @@ 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 {

View File

@ -21,7 +21,6 @@ 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;
@ -46,9 +45,6 @@ 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;
@ -61,16 +57,12 @@ final class HandshakerServiceChannel {
/* Use its own event loop thread pool to avoid blocking. */
EventLoopGroup eventGroup =
new NioEventLoopGroup(1, new DefaultThreadFactory("handshaker pool", true));
NettyChannelBuilder channelBuilder =
NettyChannelBuilder.forTarget(target)
ManagedChannel channel = NettyChannelBuilder.forTarget(target)
.channelType(NioSocketChannel.class, InetSocketAddress.class)
.directExecutor()
.eventLoopGroup(eventGroup)
.usePlaintext();
if (EXPERIMENTAL_ALTS_HANDSHAKER_KEEPALIVE_PARAMS) {
channelBuilder.keepAliveTime(10, TimeUnit.MINUTES).keepAliveTimeout(10, TimeUnit.SECONDS);
}
ManagedChannel channel = channelBuilder.build();
.usePlaintext()
.build();
return new EventLoopHoldingChannel(channel, eventGroup);
}

View File

@ -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 }

View File

@ -7,6 +7,9 @@ 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 {
@ -242,8 +245,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getGetClientStatsMethod(), getCallOptions(), request);
}
@ -252,8 +255,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request);
}
}

View File

@ -4,6 +4,9 @@ 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 {
@ -242,8 +245,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getGetGaugeMethod(), getCallOptions(), request);
}
}

View File

@ -7,6 +7,9 @@ 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 {
@ -227,15 +230,15 @@ public final class ReconnectServiceGrpc {
/**
*/
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(
public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.Messages.ReconnectParams request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getStartMethod(), getCallOptions(), request);
}
/**
*/
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getStopMethod(), getCallOptions(), request);
}
}

View File

@ -8,6 +8,9 @@ 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 {
@ -573,8 +576,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getEmptyCallMethod(), getCallOptions(), request);
}
@ -583,8 +586,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
}
@ -595,8 +598,8 @@ public final class TestServiceGrpc {
* satisfy subsequent requests.
* </pre>
*/
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(
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
}
@ -661,8 +664,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
}
}

View File

@ -8,6 +8,9 @@ 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 {
@ -196,8 +199,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
}
}

View File

@ -7,6 +7,9 @@ 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 {
@ -191,8 +194,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getConfigureMethod(), getCallOptions(), request);
}
}

View File

@ -7,6 +7,9 @@ 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 {
@ -227,15 +230,15 @@ public final class XdsUpdateHealthServiceGrpc {
/**
*/
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(
public io.grpc.testing.integration.EmptyProtos.Empty setServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getSetServingMethod(), getCallOptions(), request);
}
/**
*/
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getSetNotServingMethod(), getCallOptions(), request);
}
}

View File

@ -7,6 +7,9 @@ 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 {
@ -242,8 +245,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getGetClientStatsMethod(), getCallOptions(), request);
}
@ -252,8 +255,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request);
}
}

View File

@ -4,6 +4,9 @@ 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 {
@ -242,8 +245,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getGetGaugeMethod(), getCallOptions(), request);
}
}

View File

@ -7,6 +7,9 @@ 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 {
@ -227,15 +230,15 @@ public final class ReconnectServiceGrpc {
/**
*/
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(
public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.Messages.ReconnectParams request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getStartMethod(), getCallOptions(), request);
}
/**
*/
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getStopMethod(), getCallOptions(), request);
}
}

View File

@ -8,6 +8,9 @@ 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 {
@ -573,8 +576,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getEmptyCallMethod(), getCallOptions(), request);
}
@ -583,8 +586,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
}
@ -595,8 +598,8 @@ public final class TestServiceGrpc {
* satisfy subsequent requests.
* </pre>
*/
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(
public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
}
@ -661,8 +664,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
}
}

View File

@ -8,6 +8,9 @@ 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 {
@ -196,8 +199,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
}
}

View File

@ -7,6 +7,9 @@ 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 {
@ -191,8 +194,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getConfigureMethod(), getCallOptions(), request);
}
}

View File

@ -7,6 +7,9 @@ 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 {
@ -227,15 +230,15 @@ public final class XdsUpdateHealthServiceGrpc {
/**
*/
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(
public io.grpc.testing.integration.EmptyProtos.Empty setServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getSetServingMethod(), getCallOptions(), request);
}
/**
*/
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.testing.integration.EmptyProtos.Empty setNotServing(io.grpc.testing.integration.EmptyProtos.Empty request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getSetNotServingMethod(), getCallOptions(), request);
}
}

View File

@ -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 22
minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions { abortOnError = true }
lintOptions { abortOnError true }
publishing {
singleVariant('release') {
withSourcesJar()

View File

@ -217,6 +217,7 @@ 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);
@ -230,6 +231,7 @@ 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);

View File

@ -47,7 +47,6 @@ dependencies {
testImplementation project(':grpc-core')
testImplementation project(':grpc-testing')
testImplementation libraries.guava.testlib
testImplementation libraries.truth
signature (libraries.signature.java) {
artifact {

View File

@ -16,10 +16,8 @@
package io.grpc;
import static java.util.Objects.requireNonNull;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@ -35,7 +33,7 @@ import java.util.concurrent.TimeUnit;
* passed to the various components unambiguously.
*/
public final class Deadline implements Comparable<Deadline> {
private static final Ticker SYSTEM_TICKER = new SystemTicker();
private static final SystemTicker 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);
@ -93,7 +91,7 @@ public final class Deadline implements Comparable<Deadline> {
* @since 1.24.0
*/
public static Deadline after(long duration, TimeUnit units, Ticker ticker) {
requireNonNull(units, "units");
checkNotNull(units, "units");
return new Deadline(ticker, units.toNanos(duration), true);
}
@ -193,8 +191,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) {
requireNonNull(task, "task");
requireNonNull(scheduler, "scheduler");
checkNotNull(task, "task");
checkNotNull(scheduler, "scheduler");
return scheduler.schedule(task, deadlineNanos - ticker.nanoTime(), TimeUnit.NANOSECONDS);
}
@ -227,27 +225,37 @@ public final class Deadline implements Comparable<Deadline> {
@Override
public int compareTo(Deadline that) {
checkTicker(that);
return Long.compare(this.deadlineNanos, that.deadlineNanos);
long diff = this.deadlineNanos - that.deadlineNanos;
if (diff < 0) {
return -1;
} else if (diff > 0) {
return 1;
}
return 0;
}
@Override
public int hashCode() {
return Objects.hash(this.ticker, this.deadlineNanos);
return Arrays.asList(this.ticker, this.deadlineNanos).hashCode();
}
@Override
public boolean equals(final Object object) {
if (object == this) {
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(object instanceof Deadline)) {
if (!(o instanceof Deadline)) {
return false;
}
final Deadline that = (Deadline) object;
if (this.ticker == null ? that.ticker != null : this.ticker != that.ticker) {
final Deadline other = (Deadline) o;
if (this.ticker == null ? other.ticker != null : this.ticker != other.ticker) {
return false;
}
return this.deadlineNanos == that.deadlineNanos;
if (this.deadlineNanos != other.deadlineNanos) {
return false;
}
return true;
}
/**
@ -267,17 +275,24 @@ public final class Deadline implements Comparable<Deadline> {
* @since 1.24.0
*/
public abstract static class Ticker {
/** Returns the number of nanoseconds elapsed since this ticker's reference point in time. */
/** Returns the number of nanoseconds since this source's epoch. */
public abstract long nanoTime();
}
private static final class SystemTicker extends Ticker {
private static 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(

View File

@ -33,9 +33,9 @@ final class ConfiguratorRegistry {
@GuardedBy("this")
private boolean wasConfiguratorsSet;
@GuardedBy("this")
private List<Configurator> configurators = Collections.emptyList();
private boolean configFrozen;
@GuardedBy("this")
private int configuratorsCallCountBeforeSet = 0;
private List<Configurator> configurators = Collections.emptyList();
ConfiguratorRegistry() {}
@ -56,10 +56,11 @@ final class ConfiguratorRegistry {
* @throws IllegalStateException if this method is called more than once
*/
public synchronized void setConfigurators(List<? extends Configurator> configurators) {
if (wasConfiguratorsSet) {
if (configFrozen) {
throw new IllegalStateException("Configurators are already set");
}
this.configurators = Collections.unmodifiableList(new ArrayList<>(configurators));
configFrozen = true;
wasConfiguratorsSet = true;
}
@ -67,20 +68,10 @@ final class ConfiguratorRegistry {
* Returns a list of the configurators in this registry.
*/
public synchronized List<Configurator> getConfigurators() {
if (!wasConfiguratorsSet) {
configuratorsCallCountBeforeSet++;
}
configFrozen = true;
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;
}

View File

@ -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 RPC. */
/** Selects the config for an PRC. */
public abstract Result selectConfig(LoadBalancer.PickSubchannelArgs args);
public static final class Result {

View File

@ -48,8 +48,4 @@ public final class InternalConfiguratorRegistry {
public static boolean wasSetConfiguratorsCalled() {
return ConfiguratorRegistry.getDefaultRegistry().wasSetConfiguratorsCalled();
}
public static int getConfiguratorsCallCountBeforeSet() {
return ConfiguratorRegistry.getDefaultRegistry().getConfiguratorsCallCountBeforeSet();
}
}

View File

@ -452,6 +452,18 @@ 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() {}
}
/**
@ -1189,10 +1201,6 @@ 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.

View File

@ -22,8 +22,6 @@ 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;
@ -34,6 +32,8 @@ 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 = Sets.newHashSetWithExpectedSize(size);
Set<String> ks = new HashSet<>(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 = Maps.newHashMapWithExpectedSize(keys.size());
Map<ByteBuffer, Key<?>> asciiKeys = new HashMap<>(keys.size());
for (Key<?> key : keys) {
asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
}

View File

@ -239,9 +239,6 @@ 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
*/
@ -251,10 +248,6 @@ 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
*/
@ -262,14 +255,9 @@ public abstract class NameResolver {
public abstract void onError(Status error);
/**
* 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.
* Handles updates on resolved addresses and attributes.
*
* @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.
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
* @since 1.66
*/
public Status onResult2(ResolutionResult resolutionResult) {
@ -287,11 +275,6 @@ 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}.
*
@ -315,7 +298,6 @@ 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) {
@ -329,7 +311,6 @@ 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);
}
@ -461,18 +442,6 @@ 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() {
@ -487,7 +456,6 @@ public abstract class NameResolver {
.add("executor", executor)
.add("overrideAuthority", overrideAuthority)
.add("metricRecorder", metricRecorder)
.add("nameResolverRegistry", nameResolverRegistry)
.toString();
}
@ -507,7 +475,6 @@ public abstract class NameResolver {
builder.setOffloadExecutor(executor);
builder.setOverrideAuthority(overrideAuthority);
builder.setMetricRecorder(metricRecorder);
builder.setNameResolverRegistry(nameResolverRegistry);
builder.customArgs = cloneCustomArgs(customArgs);
return builder;
}
@ -536,7 +503,6 @@ public abstract class NameResolver {
private Executor executor;
private String overrideAuthority;
private MetricRecorder metricRecorder;
private NameResolverRegistry nameResolverRegistry;
private IdentityHashMap<Key<?>, Object> customArgs;
Builder() {
@ -643,16 +609,6 @@ 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}.
*

View File

@ -166,11 +166,6 @@ 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);
}

View File

@ -66,14 +66,6 @@ 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)) {

View File

@ -85,12 +85,14 @@ public class ConfiguratorRegistryTest {
@Override
public void run() {
assertThat(ConfiguratorRegistry.getDefaultRegistry().getConfigurators()).isEmpty();
NoopConfigurator noopConfigurator = new NoopConfigurator();
ConfiguratorRegistry.getDefaultRegistry()
.setConfigurators(Arrays.asList(noopConfigurator));
assertThat(ConfiguratorRegistry.getDefaultRegistry().getConfigurators())
.containsExactly(noopConfigurator);
assertThat(InternalConfiguratorRegistry.getConfiguratorsCallCountBeforeSet()).isEqualTo(1);
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");
}
}
}

View File

@ -16,7 +16,6 @@
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;
@ -25,7 +24,6 @@ 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;
@ -39,7 +37,9 @@ 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,6 +49,9 @@ 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
@ -62,7 +65,7 @@ public class MetadataTest {
}
};
private static class FishStreamMarshaller implements Metadata.BinaryStreamMarshaller<Fish> {
private static class FishStreamMarsaller implements Metadata.BinaryStreamMarshaller<Fish> {
@Override
public InputStream toStream(Fish fish) {
return new ByteArrayInputStream(FISH_MARSHALLER.toBytes(fish));
@ -79,7 +82,7 @@ public class MetadataTest {
}
private static final Metadata.BinaryStreamMarshaller<Fish> FISH_STREAM_MARSHALLER =
new FishStreamMarshaller();
new FishStreamMarsaller();
/** A pattern commonly used to avoid unnecessary serialization of immutable objects. */
private static final class FakeFishStream extends InputStream {
@ -118,9 +121,10 @@ public class MetadataTest {
@Test
public void noPseudoHeaders() {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> Metadata.Key.of(":test-bin", FISH_MARSHALLER));
assertThat(e).hasMessageThat().isEqualTo("Invalid character ':' in key name ':test-bin'");
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid character");
Metadata.Key.of(":test-bin", FISH_MARSHALLER);
}
@Test
@ -182,7 +186,8 @@ public class MetadataTest {
Iterator<Fish> i = metadata.getAll(KEY).iterator();
assertEquals(lance, i.next());
assertThrows(UnsupportedOperationException.class, i::remove);
thrown.expect(UnsupportedOperationException.class);
i.remove();
}
@Test
@ -266,15 +271,17 @@ public class MetadataTest {
@Test
public void shortBinaryKeyName() {
assertThrows(IllegalArgumentException.class, () -> Metadata.Key.of("-bin", FISH_MARSHALLER));
thrown.expect(IllegalArgumentException.class);
Metadata.Key.of("-bin", FISH_MARSHALLER);
}
@Test
public void invalidSuffixBinaryKeyName() {
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");
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Binary header is named");
Metadata.Key.of("nonbinary", FISH_MARSHALLER);
}
@Test
@ -408,7 +415,7 @@ public class MetadataTest {
h.put(KEY_STREAMED, salmon);
// Get using a different marshaller instance.
Fish fish = h.get(copyKey(KEY_STREAMED, new FishStreamMarshaller()));
Fish fish = h.get(copyKey(KEY_STREAMED, new FishStreamMarsaller()));
assertEquals(salmon, fish);
}

View File

@ -26,7 +26,9 @@ 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;
@ -35,6 +37,10 @@ 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()

View File

@ -19,7 +19,6 @@ 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;
@ -41,6 +40,7 @@ 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,6 +55,10 @@ 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;
@ -107,21 +111,21 @@ public class ServerInterceptorsTest {
public void npeForNullServiceDefinition() {
ServerServiceDefinition serviceDef = null;
List<ServerInterceptor> interceptors = Arrays.asList();
assertThrows(NullPointerException.class,
() -> ServerInterceptors.intercept(serviceDef, interceptors));
thrown.expect(NullPointerException.class);
ServerInterceptors.intercept(serviceDef, interceptors);
}
@Test
public void npeForNullInterceptorList() {
assertThrows(NullPointerException.class,
() -> ServerInterceptors.intercept(serviceDefinition, (List<ServerInterceptor>) null));
thrown.expect(NullPointerException.class);
ServerInterceptors.intercept(serviceDefinition, (List<ServerInterceptor>) null);
}
@Test
public void npeForNullInterceptor() {
List<ServerInterceptor> interceptors = Arrays.asList((ServerInterceptor) null);
assertThrows(NullPointerException.class,
() -> ServerInterceptors.intercept(serviceDefinition, interceptors));
thrown.expect(NullPointerException.class);
ServerInterceptors.intercept(serviceDefinition, interceptors);
}
@Test

View File

@ -18,13 +18,14 @@ 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;
@ -51,6 +52,9 @@ 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() {
@ -87,7 +91,9 @@ public class ServerServiceDefinitionTest {
ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
.addMethod(method1, methodHandler1);
assertThrows(IllegalStateException.class, () -> ssd.addMethod(diffMethod1, methodHandler2));
thrown.expect(IllegalStateException.class);
ssd.addMethod(diffMethod1, methodHandler2)
.build();
}
@Test
@ -95,7 +101,8 @@ public class ServerServiceDefinitionTest {
ServiceDescriptor sd = new ServiceDescriptor(serviceName);
ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
.addMethod(methodDef1);
assertThrows(IllegalStateException.class, ssd::build);
thrown.expect(IllegalStateException.class);
ssd.build();
}
@Test
@ -103,14 +110,16 @@ public class ServerServiceDefinitionTest {
ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
.addMethod(diffMethod1, methodHandler1);
assertThrows(IllegalStateException.class, ssd::build);
thrown.expect(IllegalStateException.class);
ssd.build();
}
@Test
public void buildMisaligned_missingMethod() {
ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd);
assertThrows(IllegalStateException.class, ssd::build);
thrown.expect(IllegalStateException.class);
ssd.build();
}
@Test

View File

@ -16,18 +16,17 @@
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;
@ -37,27 +36,32 @@ 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() {
List<MethodDescriptor<?, ?>> methods = Collections.emptyList();
NullPointerException e = assertThrows(NullPointerException.class,
() -> new ServiceDescriptor(null, methods));
assertThat(e).hasMessageThat().isEqualTo("name");
thrown.expect(NullPointerException.class);
thrown.expectMessage("name");
new ServiceDescriptor(null, Collections.<MethodDescriptor<?, ?>>emptyList());
}
@Test
public void failsOnNullMethods() {
NullPointerException e = assertThrows(NullPointerException.class,
() -> new ServiceDescriptor("name", (Collection<MethodDescriptor<?, ?>>) null));
assertThat(e).hasMessageThat().isEqualTo("methods");
thrown.expect(NullPointerException.class);
thrown.expectMessage("methods");
new ServiceDescriptor("name", (Collection<MethodDescriptor<?, ?>>) null);
}
@Test
public void failsOnNullMethod() {
List<MethodDescriptor<?, ?>> methods = Collections.singletonList(null);
NullPointerException e = assertThrows(NullPointerException.class,
() -> new ServiceDescriptor("name", methods));
assertThat(e).hasMessageThat().isEqualTo("method");
thrown.expect(NullPointerException.class);
thrown.expectMessage("method");
new ServiceDescriptor("name", Collections.<MethodDescriptor<?, ?>>singletonList(null));
}
@Test
@ -65,17 +69,15 @@ 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());
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> new ServiceDescriptor("fooService", descriptors));
StringSubject error = assertThat(e).hasMessageThat();
error.contains("service names");
error.contains("fooService");
error.contains("wrongService");
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("service names");
new ServiceDescriptor("name", descriptors);
}
@Test
@ -94,9 +96,10 @@ public class ServiceDescriptorTest {
.setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
.build());
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> new ServiceDescriptor("name", descriptors));
assertThat(e).hasMessageThat().isEqualTo("duplicate name name/method");
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("duplicate");
new ServiceDescriptor("name", descriptors);
}
@Test

View File

@ -1,118 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,66 +0,0 @@
/*
* 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 + "}";
}
}
}

View File

@ -50,12 +50,10 @@ 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.PrivateKey;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -344,10 +342,7 @@ public class GoogleAuthLibraryCallCredentialsTest {
@Test
public void serviceAccountToJwt() throws Exception {
PrivateKey privateKey;
try (InputStream server1Key = TlsTesting.loadCert("server1.key")) {
privateKey = CertificateUtils.getPrivateKey(server1Key);
}
KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
HttpTransportFactory factory = Mockito.mock(HttpTransportFactory.class);
Mockito.when(factory.create()).thenThrow(new AssertionError());
@ -355,7 +350,7 @@ public class GoogleAuthLibraryCallCredentialsTest {
ServiceAccountCredentials credentials =
ServiceAccountCredentials.newBuilder()
.setClientEmail("test-email@example.com")
.setPrivateKey(privateKey)
.setPrivateKey(pair.getPrivate())
.setPrivateKeyId("test-private-key-id")
.setHttpTransportFactory(factory)
.build();
@ -395,16 +390,13 @@ public class GoogleAuthLibraryCallCredentialsTest {
@Test
public void jwtAccessCredentialsInRequestMetadata() throws Exception {
PrivateKey privateKey;
try (InputStream server1Key = TlsTesting.loadCert("server1.key")) {
privateKey = CertificateUtils.getPrivateKey(server1Key);
}
KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
ServiceAccountCredentials credentials =
ServiceAccountCredentials.newBuilder()
.setClientId("test-client")
.setClientEmail("test-email@example.com")
.setPrivateKey(privateKey)
.setPrivateKey(pair.getPrivate())
.setPrivateKeyId("test-private-key-id")
.setQuotaProjectId("test-quota-project-id")
.build();

View File

@ -4,6 +4,9 @@ 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 {
@ -398,8 +401,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.benchmarks.proto.Messages.SimpleResponse unaryCall(io.grpc.benchmarks.proto.Messages.SimpleRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getUnaryCallMethod(), getCallOptions(), request);
}

View File

@ -4,6 +4,9 @@ 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 {
@ -177,8 +180,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.benchmarks.proto.Control.Void reportScenario(io.grpc.benchmarks.proto.Control.ScenarioResult request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getReportScenarioMethod(), getCallOptions(), request);
}
}

View File

@ -4,6 +4,9 @@ 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 {
@ -387,8 +390,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) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
public io.grpc.benchmarks.proto.Control.CoreResponse coreCount(io.grpc.benchmarks.proto.Control.CoreRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getCoreCountMethod(), getCallOptions(), request);
}
@ -397,8 +400,8 @@ public final class WorkerServiceGrpc {
* Quit this worker
* </pre>
*/
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(
public io.grpc.benchmarks.proto.Control.Void quitWorker(io.grpc.benchmarks.proto.Control.Void request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getQuitWorkerMethod(), getCallOptions(), request);
}
}

View File

@ -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 22
minSdkVersion 21
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,7 +72,6 @@ dependencies {
androidTestImplementation testFixtures(project(':grpc-core'))
testFixturesImplementation libraries.guava.testlib
testFixturesImplementation testFixtures(project(':grpc-core'))
}
import net.ltgt.gradle.errorprone.CheckSeverity

View File

@ -11,13 +11,11 @@
<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>

View File

@ -23,7 +23,6 @@ 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;
@ -40,6 +39,7 @@ 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,6 +49,7 @@ 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;
@ -76,6 +77,7 @@ 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);
@ -97,6 +99,7 @@ public final class BinderChannelSmokeTest {
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
.build();
FakeNameResolverProvider fakeNameResolverProvider;
ManagedChannel channel;
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
AtomicReference<PeerUid> clientUidCapture = new AtomicReference<>();
@ -135,6 +138,8 @@ 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()
@ -161,6 +166,7 @@ public final class BinderChannelSmokeTest {
@After
public void tearDown() throws Exception {
channel.shutdownNow();
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider);
HostServices.awaitServiceShutdown();
}
@ -229,11 +235,7 @@ public final class BinderChannelSmokeTest {
@Test
public void testConnectViaTargetUri() throws Exception {
// Compare with the <intent-filter> mapping in AndroidManifest.xml.
channel =
BinderChannelBuilder.forTarget(
"intent://authority/path#Intent;action=action1;scheme=scheme;end;", appContext)
.build();
channel = BinderChannelBuilder.forTarget(SERVER_TARGET_URI, appContext).build();
assertThat(doCall("Hello").get()).isEqualTo("Hello");
}
@ -243,10 +245,7 @@ public final class BinderChannelSmokeTest {
channel =
BinderChannelBuilder.forAddress(
AndroidComponentAddress.forBindIntent(
new Intent()
.setAction("action1")
.setData(Uri.parse("scheme://authority/path"))
.setPackage(appContext.getPackageName())),
new Intent().setAction("action1").setPackage(appContext.getPackageName())),
appContext)
.build();
assertThat(doCall("Hello").get()).isEqualTo("Hello");

View File

@ -17,7 +17,6 @@
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;
@ -25,8 +24,9 @@ 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,6 +63,7 @@ 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;
@ -100,7 +101,7 @@ public final class BinderClientTransportTest {
.build();
AndroidComponentAddress serverAddress;
BinderClientTransport transport;
BinderTransport.BinderClientTransport transport;
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
private final ObjectPool<ScheduledExecutorService> executorServicePool =
@ -153,32 +154,23 @@ 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;
}
@CanIgnoreReturnValue
public BinderClientTransportBuilder setPreAuthorizeServer(boolean preAuthorizeServer) {
factoryBuilder.setPreAuthorizeServers(preAuthorizeServer);
return this;
}
public BinderClientTransport build() {
public BinderTransport.BinderClientTransport build() {
return factoryBuilder
.buildClientTransportFactory()
.newClientTransport(serverAddress, new ClientTransportOptions(), null);
@ -197,7 +189,7 @@ public final class BinderClientTransportTest {
private static void shutdownAndTerminate(ExecutorService executorService)
throws InterruptedException {
executorService.shutdownNow();
if (!executorService.awaitTermination(TIMEOUT_SECONDS, SECONDS)) {
if (!executorService.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
throw new AssertionError("executor failed to terminate promptly");
}
}
@ -378,131 +370,47 @@ public final class BinderClientTransportTest {
}
@Test
public void testBlackHoleSecurityPolicyAuthTimeout() throws Exception {
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
public void testBlackHoleSecurityPolicyConnectTimeout() throws Exception {
transport =
new BinderClientTransportBuilder()
.setSecurityPolicy(securityPolicy)
.setPreAuthorizeServer(false)
.setSecurityPolicy(blockingSecurityPolicy)
.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();
// If the transport gave up waiting on auth, it should cancel its request.
assertThat(authRequest.isCancelled()).isTrue();
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
}
@Test
public void testBlackHoleSecurityPolicyPreAuthTimeout() throws Exception {
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
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);
transportListener.awaitTermination();
}
@Test
public void testAsyncSecurityPolicyPreAuthFailure() throws Exception {
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
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.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 {
public void testAsyncSecurityPolicyFailure() throws Exception {
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
RuntimeException exception = new NullPointerException();
securityPolicy.setAuthorizationException(exception);
transport.start(transportListener).run();
AuthRequest authRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS);
transport.shutdownNow(Status.UNAVAILABLE); // 'authRequest' remains unanswered!
transportListener.awaitShutdown();
Status transportStatus = transportListener.awaitShutdown();
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
assertThat(transportStatus.getCause()).isEqualTo(exception);
transportListener.awaitTermination();
}
@Test
public void testAsyncSecurityPolicySuccess() throws Exception {
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED);
transport.start(transportListener).run();
Status transportStatus = transportListener.awaitShutdown();
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
transportListener.awaitTermination();
assertThat(authRequest.isCancelled()).isTrue();
}
private static void startAndAwaitReady(
BinderClientTransport transport, TestTransportListener transportListener) throws Exception {
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
throws Exception {
transport.start(transportListener).run();
transportListener.awaitReady();
}
@ -521,7 +429,7 @@ public final class BinderClientTransportTest {
}
public Status awaitShutdown() throws Exception {
return shutdownStatus.get(TIMEOUT_SECONDS, SECONDS);
return shutdownStatus.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Override
@ -532,7 +440,7 @@ public final class BinderClientTransportTest {
}
public void awaitTermination() throws Exception {
isTerminated.get(TIMEOUT_SECONDS, SECONDS);
isTerminated.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Override
@ -543,7 +451,7 @@ public final class BinderClientTransportTest {
}
public void awaitReady() throws Exception {
isReady.get(TIMEOUT_SECONDS, SECONDS);
isReady.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Override
@ -659,4 +567,25 @@ 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);
}
}
}

View File

@ -106,7 +106,8 @@ public final class BinderTransportTest extends AbstractTransportTest {
options.setEagAttributes(eagAttrs());
options.setChannelLogger(transportLogger());
return new BinderClientTransport(builder.buildClientTransportFactory(), addr, options);
return new BinderTransport.BinderClientTransport(
builder.buildClientTransportFactory(), addr, options);
}
@Test

View File

@ -58,7 +58,7 @@ public final class AndroidComponentAddress extends SocketAddress {
@Nullable
private final UserHandle targetUser; // null means the same user that hosts this process.
private AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
protected AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
checkArgument(
bindIntent.getComponent() != null || bindIntent.getPackage() != null,
"'bindIntent' must be explicit. Specify either a package or ComponentName.");
@ -250,22 +250,7 @@ public final class AndroidComponentAddress extends SocketAddress {
return this;
}
/**
* 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 &#064;SystemApi's and hold certain &#064;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 &#064;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.
*/
/** See {@link AndroidComponentAddress#getTargetUser()}. */
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
public Builder setTargetUser(@Nullable UserHandle targetUser) {
this.targetUser = targetUser;

View File

@ -18,8 +18,6 @@ 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;
@ -34,42 +32,15 @@ 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>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.
* <p>See also {@link AndroidComponentAddress#getTargetUser()}.
*/
@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");
}

View File

@ -67,25 +67,4 @@ 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());
}
}

View File

@ -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>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.
* <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.
*
* @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,35 +279,6 @@ 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(
@ -321,8 +292,6 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
public ManagedChannel build() {
transportFactoryBuilder.setOffloadExecutorPool(
managedChannelImplBuilder.getOffloadExecutorPool());
setNameResolverArg(
ApiConstants.SOURCE_ANDROID_CONTEXT, transportFactoryBuilder.getSourceContext());
return super.build();
}
}

View File

@ -184,6 +184,7 @@ 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);
@ -198,6 +199,7 @@ 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);

View File

@ -53,25 +53,4 @@ 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());
}
}

View File

@ -11,8 +11,8 @@ import io.grpc.internal.ServerTransport;
import io.grpc.internal.ServerTransportListener;
/**
* Tracks which {@link BinderServerTransport} are currently active and allows invoking a {@link
* Runnable} only once all transports are terminated.
* Tracks which {@link BinderTransport.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;

View File

@ -16,12 +16,10 @@
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 {
@ -47,19 +45,6 @@ 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.
*

View File

@ -1,441 +0,0 @@
/*
* 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();
}
}

View File

@ -55,7 +55,6 @@ 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;
@ -76,19 +75,18 @@ 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 BinderClientTransport newClientTransport(
public BinderTransport.BinderClientTransport newClientTransport(
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
if (closed) {
throw new IllegalStateException("The transport factory is closed.");
}
return new BinderClientTransport(this, (AndroidComponentAddress) addr, options);
return new BinderTransport.BinderClientTransport(this, (AndroidComponentAddress) addr, options);
}
@Override
@ -130,7 +128,6 @@ 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() {
@ -142,10 +139,6 @@ 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;
@ -223,11 +216,5 @@ 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;
}
}
}

View File

@ -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.
BinderServerTransport transport =
new BinderServerTransport(
BinderTransport.BinderServerTransport transport =
new BinderTransport.BinderServerTransport(
executorServicePool,
attrsBuilder.build(),
streamTracerFactories,

View File

@ -1,126 +0,0 @@
/*
* 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));
}
}

View File

@ -19,32 +19,65 @@ 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;
@ -72,7 +105,8 @@ import javax.annotation.concurrent.ThreadSafe;
* https://github.com/grpc/proposal/blob/master/L73-java-binderchannel/wireformat.md
*/
@ThreadSafe
public abstract class BinderTransport implements IBinder.DeathRecipient {
public abstract class BinderTransport
implements LeakSafeOneWayBinder.TransactionHandler, IBinder.DeathRecipient {
private static final Logger logger = Logger.getLogger(BinderTransport.class.getName());
@ -134,10 +168,10 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
private static final int RESERVED_TRANSACTIONS = 1000;
/** The first call ID we can use. */
static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
private static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
/** The last call ID we can use. */
static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
private static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
/** The states of this transport. */
protected enum TransportState {
@ -176,14 +210,12 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
private final FlowController flowController;
/** The number of incoming bytes we've received. */
// Only read/written on @BinderThread.
private long numIncomingBytes;
private final AtomicLong numIncomingBytes;
/** The number of incoming bytes we've told our peer we've received. */
// Only read/written on @BinderThread.
private long acknowledgedIncomingBytes;
protected BinderTransport(
private BinderTransport(
ObjectPool<ScheduledExecutorService> executorServicePool,
Attributes attributes,
OneWayBinderProxy.Decorator binderDecorator,
@ -193,9 +225,10 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
this.attributes = attributes;
this.logId = logId;
scheduledExecutorService = executorServicePool.getObject();
incomingBinder = new LeakSafeOneWayBinder(this::handleTransaction);
incomingBinder = new LeakSafeOneWayBinder(this);
ongoingCalls = new ConcurrentHashMap<>();
flowController = new FlowController(TRANSACTION_BYTES_WINDOW);
numIncomingBytes = new AtomicLong();
}
// Override in child class.
@ -266,10 +299,7 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
@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")
@ -393,9 +423,8 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
}
}
@BinderThread
@VisibleForTesting
final boolean handleTransaction(int code, Parcel parcel) {
@Override
public final boolean handleTransaction(int code, Parcel parcel) {
try {
return handleTransactionInternal(code, parcel);
} catch (RuntimeException e) {
@ -411,7 +440,6 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
}
}
@BinderThread
private boolean handleTransactionInternal(int code, Parcel parcel) {
if (code < FIRST_CALL_ID) {
synchronized (this) {
@ -455,12 +483,11 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
if (inbound != null) {
inbound.handleTransaction(parcel);
}
numIncomingBytes += size;
if ((numIncomingBytes - acknowledgedIncomingBytes) > TRANSACTION_BYTES_WINDOW_FORCE_ACK) {
long nib = numIncomingBytes.addAndGet(size);
if ((nib - acknowledgedIncomingBytes) > TRANSACTION_BYTES_WINDOW_FORCE_ACK) {
synchronized (this) {
sendAcknowledgeBytes(checkNotNull(outgoingBinder), numIncomingBytes);
sendAcknowledgeBytes(checkNotNull(outgoingBinder));
}
acknowledgedIncomingBytes = numIncomingBytes;
}
return true;
}
@ -492,8 +519,10 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
protected void handlePingResponse(Parcel parcel) {}
@GuardedBy("this")
private void sendAcknowledgeBytes(OneWayBinderProxy iBinder, long n) {
private void sendAcknowledgeBytes(OneWayBinderProxy iBinder) {
// 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);
@ -524,6 +553,414 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
}
}
/** 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:

View File

@ -610,9 +610,10 @@ abstract class Inbound<L extends StreamListener> implements StreamListener.Messa
// Server-side inbound transactions.
static final class ServerInbound extends Inbound<ServerStreamListener> {
private final BinderServerTransport serverTransport;
private final BinderTransport.BinderServerTransport serverTransport;
ServerInbound(BinderServerTransport transport, Attributes attributes, int callId) {
ServerInbound(
BinderTransport.BinderServerTransport transport, Attributes attributes, int callId) {
super(transport, attributes, callId);
this.serverTransport = transport;
}

View File

@ -1,299 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,77 +0,0 @@
/*
* 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);
}
}
}

View File

@ -19,7 +19,6 @@ 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;
@ -59,7 +58,6 @@ 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);
}

View File

@ -19,6 +19,7 @@ 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;
@ -396,7 +397,8 @@ abstract class Outbound {
@GuardedBy("this")
void setDeadline(Deadline deadline) {
headers.discardAll(TIMEOUT_KEY);
headers.put(TIMEOUT_KEY, deadline.timeRemaining(TimeUnit.NANOSECONDS));
long effectiveTimeoutNanos = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
headers.put(TIMEOUT_KEY, effectiveTimeoutNanos);
}
}

View File

@ -99,7 +99,7 @@ final class PingTracker {
private synchronized void fail(Status status) {
if (!done) {
done = true;
executor.execute(() -> callback.onFailure(status));
executor.execute(() -> callback.onFailure(status.asException()));
}
}

View File

@ -23,22 +23,14 @@ 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;
@ -93,8 +85,6 @@ final class ServiceBinding implements Bindable, ServiceConnection {
private final Observer observer;
private final Executor mainThreadExecutor;
private static volatile Method queryIntentServicesAsUserMethod;
@GuardedBy("this")
private State state;
@ -193,27 +183,18 @@ final class ServiceBinding implements Bindable, ServiceConnection {
bindResult = context.bindService(bindIntent, conn, flags);
break;
case BIND_SERVICE_AS_USER:
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+");
}
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
break;
case DEVICE_POLICY_BIND_SEVICE_ADMIN:
DevicePolicyManager devicePolicyManager =
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
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+");
}
bindResult =
devicePolicyManager.bindDeviceAdminServiceAsUser(
channelCredentials.getDevicePolicyAdminComponentName(),
bindIntent,
conn,
flags,
targetUserHandle);
break;
}
if (!bindResult) {
@ -266,71 +247,6 @@ 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;

View File

@ -1,60 +0,0 @@
/*
* 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);
}
}

View File

@ -22,13 +22,12 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.robolectric.Shadows.shadowOf;
import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.os.Looper;
import androidx.lifecycle.LifecycleService;
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;
@ -43,143 +42,90 @@ 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.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.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ServiceController;
@RunWith(ParameterizedRobolectricTestRunner.class)
@LooperMode(Mode.INSTRUMENTATION_TEST)
@RunWith(RobolectricTestRunner.class)
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 final ArrayBlockingQueue<SettableFuture<Status>> statusesToSet =
new ArrayBlockingQueue<>(128);
private ServiceController<SomeService> controller;
private SomeService service;
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() {
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);
controller = Robolectric.buildService(SomeService.class);
service = controller.create().get();
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()));
AndroidComponentAddress listenAddress = AndroidComponentAddress.forContext(service);
ScheduledExecutorService executor = service.getExecutor();
channel =
BinderChannelBuilder.forAddress(listenAddress, context)
.preAuthorizeServers(preAuthServersParam)
.executor(executor)
.scheduledExecutorService(executor)
.offloadExecutor(executor)
.build();
idleLoopers();
}
@After
public void tearDown() {
channel.shutdownNow();
server.shutdownNow();
controller.destroy();
}
@Test
public void testAsyncServerSecurityPolicy_failed_returnsFailureStatus() throws Exception {
ListenableFuture<Status> status = makeCall();
statusesToSet.take().set(Status.ALREADY_EXISTS);
service.setSecurityPolicyStatusWhenReady(Status.ALREADY_EXISTS);
idleLoopers();
assertThat(status.get().getCode()).isEqualTo(Status.Code.ALREADY_EXISTS);
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.ALREADY_EXISTS);
}
@Test
public void testAsyncServerSecurityPolicy_failedFuture_failsWithCodeInternal() throws Exception {
ListenableFuture<Status> status = makeCall();
statusesToSet.take().setException(new IllegalStateException("oops"));
service.setSecurityPolicyFailed(new IllegalStateException("oops"));
idleLoopers();
assertThat(status.get().getCode()).isEqualTo(Status.Code.INTERNAL);
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.INTERNAL);
}
@Test
public void testAsyncServerSecurityPolicy_allowed_returnsOkStatus() throws Exception {
ListenableFuture<Status> status = makeCall();
statusesToSet.take().set(Status.OK);
service.setSecurityPolicyStatusWhenReady(Status.OK);
idleLoopers();
assertThat(status.get().getCode()).isEqualTo(Status.Code.OK);
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.OK);
}
private ListenableFuture<Status> makeCall() {
ClientCall<Empty, Empty> call = channel.newCall(getMethodDescriptor(), CallOptions.DEFAULT);
ClientCall<Empty, Empty> call =
channel.newCall(
getMethodDescriptor(), CallOptions.DEFAULT.withExecutor(service.getExecutor()));
ListenableFuture<Empty> responseFuture =
ClientCalls.futureUnaryCall(call, Empty.getDefaultInstance());
idleLoopers();
return Futures.catching(
Futures.transform(responseFuture, unused -> Status.OK, directExecutor()),
StatusRuntimeException.class,
@ -187,6 +133,10 @@ 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());
@ -197,4 +147,106 @@ 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}. */
}
}

View File

@ -357,7 +357,7 @@ public final class SecurityPoliciesTest {
}
@Test
@Config(sdk = Config.OLDEST_SDK)
@Config(sdk = 21)
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 = Config.OLDEST_SDK)
@Config(sdk = 21)
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 = Config.OLDEST_SDK)
@Config(sdk = 21)
public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception {
policy = SecurityPolicies.isProfileOwner(appContext);
@ -425,7 +425,7 @@ public final class SecurityPoliciesTest {
}
@Test
@Config(sdk = Config.OLDEST_SDK)
@Config(sdk = 21)
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 = Config.OLDEST_SDK)
@Config(sdk = 21)
public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception {
policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);

View File

@ -16,6 +16,7 @@
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;
@ -28,9 +29,11 @@ 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.MockServerTransportListener;
import io.grpc.internal.ServerStream;
import io.grpc.internal.ServerTransportListener;
import java.util.concurrent.ScheduledExecutorService;
import org.junit.Before;
import org.junit.Rule;
@ -52,22 +55,21 @@ public final class BinderServerTransportTest {
@Rule public MockitoRule mocks = MockitoJUnit.rule();
private final ScheduledExecutorService executorService = new MainThreadScheduledExecutorService();
private MockServerTransportListener transportListener;
private final TestTransportListener transportListener = new TestTransportListener();
@Mock IBinder mockBinder;
BinderServerTransport transport;
BinderTransport.BinderServerTransport transport;
@Before
public void setUp() throws Exception {
transport =
new BinderServerTransport(
new BinderTransport.BinderServerTransport(
new FixedObjectPool<>(executorService),
Attributes.EMPTY,
ImmutableList.of(),
OneWayBinderProxy.IDENTITY_DECORATOR,
mockBinder);
transportListener = new MockServerTransportListener(transport);
}
@Test
@ -80,6 +82,34 @@ public final class BinderServerTransportTest {
transport.shutdownNow(Status.UNKNOWN.withDescription("reasons"));
shadowOf(Looper.getMainLooper()).idle();
assertThat(transportListener.isTerminated()).isTrue();
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;
}
}
}

View File

@ -1,115 +0,0 @@
/*
* 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);
});
}
}

View File

@ -1,531 +0,0 @@
/*
* 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);
}
}
}

View File

@ -96,7 +96,7 @@ public final class PingTrackerTest {
private int numCallbacks;
private boolean success;
private boolean failure;
private Status failureStatus;
private Throwable failureException;
private long roundtripTimeNanos;
@Override
@ -107,10 +107,10 @@ public final class PingTrackerTest {
}
@Override
public synchronized void onFailure(Status failureStatus) {
public synchronized void onFailure(Throwable failureException) {
numCallbacks += 1;
failure = true;
this.failureStatus = failureStatus;
this.failureException = failureException;
}
public void assertNotCalled() {
@ -130,13 +130,13 @@ public final class PingTrackerTest {
public void assertFailure(Status status) {
assertThat(numCallbacks).isEqualTo(1);
assertThat(failure).isTrue();
assertThat(failureStatus).isSameInstanceAs(status);
assertThat(((StatusException) failureException).getStatus()).isSameInstanceAs(status);
}
public void assertFailure(Status.Code statusCode) {
assertThat(numCallbacks).isEqualTo(1);
assertThat(failure).isTrue();
assertThat(failureStatus.getCode()).isEqualTo(statusCode);
assertThat(((StatusException) failureException).getStatus().getCode()).isEqualTo(statusCode);
}
}
}

View File

@ -1,281 +0,0 @@
/*
* 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() {}
}

View File

@ -19,7 +19,6 @@ 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;
@ -28,8 +27,6 @@ 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;
@ -37,7 +34,6 @@ 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;
@ -63,7 +59,6 @@ public final class ServiceBindingTest {
private Application appContext;
private ComponentName serviceComponent;
private ServiceInfo serviceInfo = new ServiceInfo();
private ShadowApplication shadowApplication;
private TestObserver observer;
private ServiceBinding binding;
@ -72,17 +67,13 @@ 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();
@ -285,93 +276,6 @@ 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 {
@ -380,7 +284,7 @@ public final class ServiceBindingTest {
allowBindDeviceAdminForUser(appContext, adminComponent, /* userId= */ 0);
binding =
newBuilder()
.setTargetUserHandle(UserHandle.getUserHandleForUid(/* uid= */ 0))
.setTargetUserHandle(UserHandle.getUserHandleForUid(/* userId= */ 0))
.setTargetUserHandle(generateUserHandle(/* userId= */ 0))
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
.build();

View File

@ -1,61 +0,0 @@
/*
* 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));
}
}

View File

@ -1,83 +0,0 @@
/*
* 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();
}
}
}

View File

@ -21,11 +21,11 @@ subprojects {
apply plugin: "net.ltgt.errorprone"
group = "io.grpc"
version = "1.76.0-SNAPSHOT" // CURRENT_GRPC_VERSION
version = "1.71.0" // 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/sources/proto/[^/]+/java/.*",
"|.*/build/generated/source/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 = "${baseUrl}/staging/deployByRepositoryId/" + rootProject.repositoryId
stagingUrl = 'https://oss.sonatype.org/service/local/staging/deployByRepositoryId/' +
rootProject.repositoryId
} else {
stagingUrl = "${baseUrl}/staging/deploy/maven2/"
stagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
}
credentials {
if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) {
@ -402,7 +402,7 @@ subprojects {
}
}
def releaseUrl = stagingUrl
def snapshotUrl = 'https://central.sonatype.com/repository/maven-snapshots/'
def snapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
url = version.endsWith('SNAPSHOT') ? snapshotUrl : releaseUrl
}
}
@ -410,7 +410,7 @@ subprojects {
}
signing {
required = false
required false
sign publishing.publications.maven
}

View File

@ -27,11 +27,7 @@ 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://archive.apache.org/dist/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz | \
RUN curl -Ls https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz | \
tar xz -C /var/local
ENV PATH /var/local/cmake-3.26.3-linux-x86_64/bin:/var/local/apache-maven-3.8.8/bin:$PATH
ENV PATH /var/local/apache-maven-3.8.8/bin:$PATH

View File

@ -10,11 +10,5 @@ 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

View File

@ -9,11 +9,5 @@ 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

View File

@ -2,8 +2,16 @@
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 \
@ -13,12 +21,15 @@ 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 -PskipCodegen=true"
GRADLE_FLAGS="-Pandroid.useAndroidX=true -Dorg.gradle.jvmargs=-Xmx1024m"
./gradlew $GRADLE_FLAGS :grpc-android-interop-testing:assembleDebug
./gradlew $GRADLE_FLAGS :grpc-android-interop-testing:assembleDebugAndroidTest

View File

@ -9,6 +9,9 @@ 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
@ -27,18 +30,10 @@ 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
@ -103,7 +98,6 @@ 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

View File

@ -1,17 +0,0 @@
# 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"
}

View File

@ -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: 240
timeout_mins: 120
action {
define_artifacts {

View File

@ -13,5 +13,5 @@ action {
}
env_vars {
key: "PSM_TEST_SUITE"
value: "light"
value: "fallback"
}

View File

@ -1,17 +0,0 @@
# 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"
}

View File

@ -51,9 +51,9 @@ fi
export GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1g'"
# Make protobuf discoverable by :grpc-compiler
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
export LD_LIBRARY_PATH=/tmp/protobuf/lib
export LDFLAGS=-L/tmp/protobuf/lib
export CXXFLAGS="-I/tmp/protobuf/include"
./gradlew grpc-compiler:clean $GRADLE_FLAGS

View File

@ -2,7 +2,7 @@
# Location of the continuous shell script in repository.
build_file: "grpc-java/buildscripts/kokoro/windows.bat"
timeout_mins: 90
timeout_mins: 45
# We always build mvn artifacts.
action {

View File

@ -15,21 +15,19 @@ 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 "%VS170COMNTOOLS%\..\..\VC\Auxiliary\Build\vcvars32.bat" || exit /b 1
call "%VS140COMNTOOLS%\vsvars32.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 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 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 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'"
@ -52,32 +50,3 @@ 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

View File

@ -14,21 +14,19 @@ 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 "%VS170COMNTOOLS%\..\..\VC\Auxiliary\Build\vcvars64.bat" || exit /b 1
call "%VS140COMNTOOLS%\..\..\VC\bin\amd64\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 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 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 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'"
@ -36,32 +34,3 @@ 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