mirror of https://github.com/grpc/grpc-java.git
Compare commits
46 Commits
Author | SHA1 | Date |
---|---|---|
|
c7202c0db5 | |
|
028afbe352 | |
|
afdbecb235 | |
|
2039266ebc | |
|
43bef65cf9 | |
|
437e03dc98 | |
|
6462ef9a11 | |
|
95d16d85c8 | |
|
f50726d32e | |
|
06707f7c38 | |
|
efcdebb904 | |
|
f30964ab82 | |
|
7040417eee | |
|
a40c8cf5a4 | |
|
8b46ad58c3 | |
|
d947c80f99 | |
|
6ffcbd927e | |
|
36fe276a50 | |
|
ba0a7329da | |
|
28f14255ce | |
|
7e982e48a1 | |
|
c3ef1ab034 | |
|
8f09b96899 | |
|
42e1829b37 | |
|
c4256add4d | |
|
6ff8ecac09 | |
|
80217275db | |
|
2e96fbf1e8 | |
|
a37d3eb349 | |
|
1fc4ab0bb2 | |
|
6935d3a115 | |
|
d7d70c6905 | |
|
d352540a02 | |
|
5a8326f1c7 | |
|
a8de9f07ab | |
|
9d191b31b5 | |
|
01bd63d88f | |
|
94532a6b56 | |
|
6dfa03c51c | |
|
919370172d | |
|
ca99a8c478 | |
|
2ee4f9b488 | |
|
74aee11389 | |
|
64322c3243 | |
|
af7efeb9f5 | |
|
ebc6d3e932 |
|
@ -102,6 +102,9 @@ jobs:
|
|||
- name: Run bazel build
|
||||
run: bazelisk build //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||
|
||||
- name: Run bazel test
|
||||
run: bazelisk test //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||
|
||||
- name: Run example bazel build
|
||||
run: bazelisk build //... --enable_bzlmod=${{ matrix.bzlmod }}
|
||||
working-directory: ./examples
|
||||
|
|
|
@ -11,8 +11,6 @@ for general contribution guidelines.
|
|||
- [ejona86](https://github.com/ejona86), Google LLC
|
||||
- [jdcormie](https://github.com/jdcormie), Google LLC
|
||||
- [kannanjgithub](https://github.com/kannanjgithub), Google LLC
|
||||
- [larry-safran](https://github.com/larry-safran), Google LLC
|
||||
- [markb74](https://github.com/markb74), Google LLC
|
||||
- [ran-su](https://github.com/ran-su), Google LLC
|
||||
- [sergiitk](https://github.com/sergiitk), Google LLC
|
||||
- [temawi](https://github.com/temawi), Google LLC
|
||||
|
@ -26,7 +24,9 @@ for general contribution guidelines.
|
|||
- [ericgribkoff](https://github.com/ericgribkoff)
|
||||
- [jiangtaoli2016](https://github.com/jiangtaoli2016)
|
||||
- [jtattermusch](https://github.com/jtattermusch)
|
||||
- [larry-safran](https://github.com/larry-safran)
|
||||
- [louiscryan](https://github.com/louiscryan)
|
||||
- [markb74](https://github.com/markb74)
|
||||
- [nicolasnoble](https://github.com/nicolasnoble)
|
||||
- [nmittler](https://github.com/nmittler)
|
||||
- [sanjaypujare](https://github.com/sanjaypujare)
|
||||
|
|
59
MODULE.bazel
59
MODULE.bazel
|
@ -2,13 +2,13 @@ module(
|
|||
name = "grpc-java",
|
||||
compatibility_level = 0,
|
||||
repo_name = "io_grpc_grpc_java",
|
||||
version = "1.74.0-SNAPSHOT", # CURRENT_GRPC_VERSION
|
||||
version = "1.76.0-SNAPSHOT", # CURRENT_GRPC_VERSION
|
||||
)
|
||||
|
||||
# GRPC_DEPS_START
|
||||
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||
"com.google.android:annotations:4.1.1.4",
|
||||
"com.google.api.grpc:proto-google-common-protos:2.51.0",
|
||||
"com.google.api.grpc:proto-google-common-protos:2.59.2",
|
||||
"com.google.auth:google-auth-library-credentials:1.24.1",
|
||||
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
||||
"com.google.auto.value:auto-value-annotations:1.11.0",
|
||||
|
@ -19,24 +19,24 @@ 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.1",
|
||||
"com.google.s2a.proto.v2:s2a-proto:0.1.2",
|
||||
"com.google.truth:truth:1.4.2",
|
||||
"com.squareup.okhttp:okhttp:2.7.5",
|
||||
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
||||
"io.netty:netty-buffer:4.1.110.Final",
|
||||
"io.netty:netty-codec-http2:4.1.110.Final",
|
||||
"io.netty:netty-codec-http:4.1.110.Final",
|
||||
"io.netty:netty-codec-socks:4.1.110.Final",
|
||||
"io.netty:netty-codec:4.1.110.Final",
|
||||
"io.netty:netty-common:4.1.110.Final",
|
||||
"io.netty:netty-handler-proxy:4.1.110.Final",
|
||||
"io.netty:netty-handler:4.1.110.Final",
|
||||
"io.netty:netty-resolver:4.1.110.Final",
|
||||
"io.netty:netty-buffer:4.1.124.Final",
|
||||
"io.netty:netty-codec-http2:4.1.124.Final",
|
||||
"io.netty:netty-codec-http:4.1.124.Final",
|
||||
"io.netty:netty-codec-socks:4.1.124.Final",
|
||||
"io.netty:netty-codec:4.1.124.Final",
|
||||
"io.netty:netty-common:4.1.124.Final",
|
||||
"io.netty:netty-handler-proxy:4.1.124.Final",
|
||||
"io.netty:netty-handler:4.1.124.Final",
|
||||
"io.netty:netty-resolver:4.1.124.Final",
|
||||
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
||||
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.110.Final",
|
||||
"io.netty:netty-transport:4.1.110.Final",
|
||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.124.Final",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.124.Final",
|
||||
"io.netty:netty-transport:4.1.124.Final",
|
||||
"io.opencensus:opencensus-api:0.31.0",
|
||||
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
||||
"io.perfmark:perfmark-api:0.27.0",
|
||||
|
@ -46,36 +46,17 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
|||
]
|
||||
# GRPC_DEPS_END
|
||||
|
||||
bazel_dep(name = "bazel_jar_jar", version = "0.1.7")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||
bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5")
|
||||
|
||||
# CEL Spec may be removed when cncf/xds MODULE is no longer using protobuf 27.x
|
||||
bazel_dep(name = "cel-spec", repo_name = "dev_cel", version = "0.15.0")
|
||||
bazel_dep(name = "grpc", repo_name = "com_github_grpc_grpc", version = "1.56.3.bcr.1")
|
||||
bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58")
|
||||
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
|
||||
# Protobuf 25.5+ is incompatible with Bazel 7 with bzlmod
|
||||
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "24.4")
|
||||
bazel_dep(name = "rules_cc", version = "0.0.9")
|
||||
bazel_dep(name = "rules_java", version = "5.3.5")
|
||||
bazel_dep(name = "rules_go", repo_name = "io_bazel_rules_go", version = "0.46.0")
|
||||
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
||||
bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
|
||||
|
||||
non_module_deps = use_extension("//:repositories.bzl", "grpc_java_repositories_extension")
|
||||
|
||||
use_repo(
|
||||
non_module_deps,
|
||||
"com_github_cncf_xds",
|
||||
"envoy_api",
|
||||
)
|
||||
|
||||
grpc_repo_deps_ext = use_extension("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_repo_deps_ext")
|
||||
|
||||
use_repo(
|
||||
grpc_repo_deps_ext,
|
||||
"com_envoyproxy_protoc_gen_validate",
|
||||
"opencensus_proto",
|
||||
)
|
||||
|
||||
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||
|
||||
maven.install(
|
||||
|
@ -202,7 +183,3 @@ maven.override(
|
|||
coordinates = "io.grpc:grpc-util",
|
||||
target = "@io_grpc_grpc_java//util",
|
||||
)
|
||||
|
||||
switched_rules = use_extension("@com_google_googleapis//:extensions.bzl", "switched_rules")
|
||||
|
||||
switched_rules.use_languages(java = True)
|
||||
|
|
30
README.md
30
README.md
|
@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start
|
|||
guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC
|
||||
basics](https://grpc.io/docs/languages/java/basics).
|
||||
|
||||
The [examples](https://github.com/grpc/grpc-java/tree/v1.73.0/examples) and the
|
||||
[Android example](https://github.com/grpc/grpc-java/tree/v1.73.0/examples/android)
|
||||
The [examples](https://github.com/grpc/grpc-java/tree/v1.75.0/examples) and the
|
||||
[Android example](https://github.com/grpc/grpc-java/tree/v1.75.0/examples/android)
|
||||
are standalone projects that showcase the usage of gRPC.
|
||||
|
||||
Download
|
||||
|
@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
|||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty-shaded</artifactId>
|
||||
<version>1.73.0</version>
|
||||
<version>1.75.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
<version>1.73.0</version>
|
||||
<version>1.75.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
<version>1.73.0</version>
|
||||
<version>1.75.0</version>
|
||||
</dependency>
|
||||
<dependency> <!-- necessary for Java 9+ -->
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
|
@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
|||
|
||||
Or for Gradle with non-Android, add to your dependencies:
|
||||
```gradle
|
||||
runtimeOnly 'io.grpc:grpc-netty-shaded:1.73.0'
|
||||
implementation 'io.grpc:grpc-protobuf:1.73.0'
|
||||
implementation 'io.grpc:grpc-stub:1.73.0'
|
||||
runtimeOnly 'io.grpc:grpc-netty-shaded:1.75.0'
|
||||
implementation 'io.grpc:grpc-protobuf:1.75.0'
|
||||
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
||||
```
|
||||
|
||||
For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and
|
||||
`grpc-protobuf-lite` instead of `grpc-protobuf`:
|
||||
```gradle
|
||||
implementation 'io.grpc:grpc-okhttp:1.73.0'
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.73.0'
|
||||
implementation 'io.grpc:grpc-stub:1.73.0'
|
||||
implementation 'io.grpc:grpc-okhttp:1.75.0'
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0'
|
||||
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
||||
```
|
||||
|
||||
|
@ -99,7 +99,7 @@ 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.73.0
|
||||
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.75.0
|
||||
|
||||
Development snapshots are available in [Sonatypes's snapshot
|
||||
repository](https://central.sonatype.com/repository/maven-snapshots/).
|
||||
|
@ -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.73.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.75.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -161,7 +161,7 @@ protobuf {
|
|||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.73.0'
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -194,7 +194,7 @@ protobuf {
|
|||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.73.0'
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
|
|
@ -400,7 +400,8 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver
|
|||
1.59.x | 4.1.97.Final | 2.0.61.Final
|
||||
1.60.x-1.66.x | 4.1.100.Final | 2.0.61.Final
|
||||
1.67.x-1.70.x | 4.1.110.Final | 2.0.65.Final
|
||||
1.71.x- | 4.1.110.Final | 2.0.70.Final
|
||||
1.71.x-1.74.x | 4.1.110.Final | 2.0.70.Final
|
||||
1.75.x- | 4.1.124.Final | 2.0.72.Final
|
||||
|
||||
_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
|
||||
|
||||
|
|
|
@ -22,20 +22,19 @@ load("//:repositories.bzl", "grpc_java_repositories")
|
|||
|
||||
grpc_java_repositories()
|
||||
|
||||
load("@bazel_jar_jar//:jar_jar.bzl", "jar_jar_repositories")
|
||||
|
||||
jar_jar_repositories()
|
||||
|
||||
load("@com_google_protobuf//:protobuf_deps.bzl", "PROTOBUF_MAVEN_ARTIFACTS")
|
||||
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
|
||||
|
||||
protobuf_deps()
|
||||
|
||||
load("@envoy_api//bazel:repositories.bzl", "api_dependencies")
|
||||
|
||||
api_dependencies()
|
||||
|
||||
load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
|
||||
|
||||
switched_rules_by_language(
|
||||
name = "com_google_googleapis_imports",
|
||||
java = True,
|
||||
)
|
||||
|
||||
maven_install(
|
||||
|
|
|
@ -10,7 +10,7 @@ repositories {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace 'io.grpc.android.integrationtest'
|
||||
namespace = 'io.grpc.android.integrationtest'
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
|
@ -41,7 +41,7 @@ android {
|
|||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
multiDexEnabled = true
|
||||
}
|
||||
buildTypes {
|
||||
debug { minifyEnabled false }
|
||||
|
|
|
@ -7,20 +7,20 @@ plugins {
|
|||
description = 'gRPC: Android'
|
||||
|
||||
android {
|
||||
namespace 'io.grpc.android'
|
||||
namespace = 'io.grpc.android'
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
compileSdkVersion 34
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
lintOptions { abortOnError true }
|
||||
lintOptions { abortOnError = true }
|
||||
publishing {
|
||||
singleVariant('release') {
|
||||
withSourcesJar()
|
||||
|
|
|
@ -217,7 +217,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
|||
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
||||
unregisterRunnable =
|
||||
new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void run() {
|
||||
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
||||
|
@ -231,7 +230,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
|||
context.registerReceiver(networkReceiver, networkIntentFilter);
|
||||
unregisterRunnable =
|
||||
new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void run() {
|
||||
context.unregisterReceiver(networkReceiver);
|
||||
|
|
|
@ -1189,6 +1189,10 @@ public abstract class LoadBalancer {
|
|||
* Returns a {@link SynchronizationContext} that runs tasks in the same Synchronization Context
|
||||
* as that the callback methods on the {@link LoadBalancer} interface are run in.
|
||||
*
|
||||
* <p>Work added to the synchronization context might not run immediately, so LB implementations
|
||||
* must be careful to ensure that any assumptions still hold when it is executed. In particular,
|
||||
* the LB might have been shut down or subchannels might have changed state.
|
||||
*
|
||||
* <p>Pro-tip: in order to call {@link SynchronizationContext#schedule}, you need to provide a
|
||||
* {@link ScheduledExecutorService}. {@link #getScheduledExecutorService} is provided for your
|
||||
* convenience.
|
||||
|
|
|
@ -22,6 +22,8 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -32,8 +34,6 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -325,7 +325,7 @@ public final class Metadata {
|
|||
if (isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> ks = new HashSet<>(size);
|
||||
Set<String> ks = Sets.newHashSetWithExpectedSize(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
ks.add(new String(name(i), 0 /* hibyte */));
|
||||
}
|
||||
|
@ -526,7 +526,7 @@ public final class Metadata {
|
|||
public void merge(Metadata other, Set<Key<?>> keys) {
|
||||
Preconditions.checkNotNull(other, "other");
|
||||
// Use ByteBuffer for equals and hashCode.
|
||||
Map<ByteBuffer, Key<?>> asciiKeys = new HashMap<>(keys.size());
|
||||
Map<ByteBuffer, Key<?>> asciiKeys = Maps.newHashMapWithExpectedSize(keys.size());
|
||||
for (Key<?> key : keys) {
|
||||
asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
|
||||
}
|
||||
|
|
|
@ -239,6 +239,9 @@ public abstract class NameResolver {
|
|||
* {@link ResolutionResult#getAddressesOrError()} is empty, {@link #onError(Status)} will be
|
||||
* called.
|
||||
*
|
||||
* <p>Newer NameResolver implementations should prefer calling onResult2. This method exists to
|
||||
* facilitate older {@link Listener} implementations to migrate to {@link Listener2}.
|
||||
*
|
||||
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
|
||||
* @since 1.21.0
|
||||
*/
|
||||
|
@ -248,6 +251,10 @@ public abstract class NameResolver {
|
|||
* Handles a name resolving error from the resolver. The listener is responsible for eventually
|
||||
* invoking {@link NameResolver#refresh()} to re-attempt resolution.
|
||||
*
|
||||
* <p>New NameResolver implementations should prefer calling onResult2 which will have the
|
||||
* address resolution error in {@link ResolutionResult}'s addressesOrError. This method exists
|
||||
* to facilitate older implementations using {@link Listener} to migrate to {@link Listener2}.
|
||||
*
|
||||
* @param error a non-OK status
|
||||
* @since 1.21.0
|
||||
*/
|
||||
|
@ -255,9 +262,14 @@ public abstract class NameResolver {
|
|||
public abstract void onError(Status error);
|
||||
|
||||
/**
|
||||
* Handles updates on resolved addresses and attributes.
|
||||
* Handles updates on resolved addresses and attributes. Must be called from the same
|
||||
* {@link SynchronizationContext} available in {@link NameResolver.Args} that is passed
|
||||
* from the channel.
|
||||
*
|
||||
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
|
||||
* @param resolutionResult the resolved server addresses or error in address resolution,
|
||||
* attributes, and Service Config or error
|
||||
* @return status indicating whether the resolutionResult was accepted by the listener,
|
||||
* typically the result from a load balancer.
|
||||
* @since 1.66
|
||||
*/
|
||||
public Status onResult2(ResolutionResult resolutionResult) {
|
||||
|
|
|
@ -166,6 +166,11 @@ public final class NameResolverRegistry {
|
|||
} catch (ClassNotFoundException e) {
|
||||
logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
|
||||
}
|
||||
try {
|
||||
list.add(Class.forName("io.grpc.binder.internal.IntentNameResolverProvider"));
|
||||
} catch (ClassNotFoundException e) {
|
||||
logger.log(Level.FINE, "Unable to find IntentNameResolverProvider", e);
|
||||
}
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,28 +6,28 @@ plugins {
|
|||
description = 'gRPC BinderChannel'
|
||||
|
||||
android {
|
||||
namespace 'io.grpc.binder'
|
||||
namespace = 'io.grpc.binder'
|
||||
compileSdkVersion 34
|
||||
compileOptions {
|
||||
sourceCompatibility 1.8
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
multiDexEnabled = true
|
||||
}
|
||||
lintOptions { abortOnError false }
|
||||
lintOptions { abortOnError = false }
|
||||
publishing {
|
||||
singleVariant('release') {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
}
|
||||
testFixtures { enable true }
|
||||
testFixtures { enable = true }
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -72,6 +72,7 @@ dependencies {
|
|||
androidTestImplementation testFixtures(project(':grpc-core'))
|
||||
|
||||
testFixturesImplementation libraries.guava.testlib
|
||||
testFixturesImplementation testFixtures(project(':grpc-core'))
|
||||
}
|
||||
|
||||
import net.ltgt.gradle.errorprone.CheckSeverity
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="action1"/>
|
||||
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name="io.grpc.binder.HostServices$HostService2" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="action2"/>
|
||||
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
|
|
@ -23,6 +23,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
@ -39,7 +40,6 @@ import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
|||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.NameResolverRegistry;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCall.Listener;
|
||||
import io.grpc.ServerCallHandler;
|
||||
|
@ -49,7 +49,6 @@ import io.grpc.ServerServiceDefinition;
|
|||
import io.grpc.Status.Code;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.testing.FakeNameResolverProvider;
|
||||
import io.grpc.stub.ClientCalls;
|
||||
import io.grpc.stub.MetadataUtils;
|
||||
import io.grpc.stub.ServerCalls;
|
||||
|
@ -77,7 +76,6 @@ public final class BinderChannelSmokeTest {
|
|||
|
||||
private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100;
|
||||
private static final String MSG = "Some text which will be repeated many many times";
|
||||
private static final String SERVER_TARGET_URI = "fake://server";
|
||||
private static final Metadata.Key<PoisonParcelable> POISON_KEY =
|
||||
ParcelableUtils.metadataKey("poison-bin", PoisonParcelable.CREATOR);
|
||||
|
||||
|
@ -99,7 +97,6 @@ public final class BinderChannelSmokeTest {
|
|||
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
|
||||
.build();
|
||||
|
||||
FakeNameResolverProvider fakeNameResolverProvider;
|
||||
ManagedChannel channel;
|
||||
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
|
||||
AtomicReference<PeerUid> clientUidCapture = new AtomicReference<>();
|
||||
|
@ -138,8 +135,6 @@ public final class BinderChannelSmokeTest {
|
|||
PeerUids.newPeerIdentifyingServerInterceptor());
|
||||
|
||||
AndroidComponentAddress serverAddress = HostServices.allocateService(appContext);
|
||||
fakeNameResolverProvider = new FakeNameResolverProvider(SERVER_TARGET_URI, serverAddress);
|
||||
NameResolverRegistry.getDefaultRegistry().register(fakeNameResolverProvider);
|
||||
HostServices.configureService(
|
||||
serverAddress,
|
||||
HostServices.serviceParamsBuilder()
|
||||
|
@ -166,7 +161,6 @@ public final class BinderChannelSmokeTest {
|
|||
@After
|
||||
public void tearDown() throws Exception {
|
||||
channel.shutdownNow();
|
||||
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider);
|
||||
HostServices.awaitServiceShutdown();
|
||||
}
|
||||
|
||||
|
@ -235,7 +229,11 @@ public final class BinderChannelSmokeTest {
|
|||
|
||||
@Test
|
||||
public void testConnectViaTargetUri() throws Exception {
|
||||
channel = BinderChannelBuilder.forTarget(SERVER_TARGET_URI, appContext).build();
|
||||
// Compare with the <intent-filter> mapping in AndroidManifest.xml.
|
||||
channel =
|
||||
BinderChannelBuilder.forTarget(
|
||||
"intent://authority/path#Intent;action=action1;scheme=scheme;end;", appContext)
|
||||
.build();
|
||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||
}
|
||||
|
||||
|
@ -245,7 +243,10 @@ public final class BinderChannelSmokeTest {
|
|||
channel =
|
||||
BinderChannelBuilder.forAddress(
|
||||
AndroidComponentAddress.forBindIntent(
|
||||
new Intent().setAction("action1").setPackage(appContext.getPackageName())),
|
||||
new Intent()
|
||||
.setAction("action1")
|
||||
.setData(Uri.parse("scheme://authority/path"))
|
||||
.setPackage(appContext.getPackageName())),
|
||||
appContext)
|
||||
.build();
|
||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||
|
|
|
@ -100,7 +100,7 @@ public final class BinderClientTransportTest {
|
|||
.build();
|
||||
|
||||
AndroidComponentAddress serverAddress;
|
||||
BinderTransport.BinderClientTransport transport;
|
||||
BinderClientTransport transport;
|
||||
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
|
||||
|
||||
private final ObjectPool<ScheduledExecutorService> executorServicePool =
|
||||
|
@ -172,7 +172,13 @@ public final class BinderClientTransportTest {
|
|||
return this;
|
||||
}
|
||||
|
||||
public BinderTransport.BinderClientTransport build() {
|
||||
@CanIgnoreReturnValue
|
||||
public BinderClientTransportBuilder setPreAuthorizeServer(boolean preAuthorizeServer) {
|
||||
factoryBuilder.setPreAuthorizeServers(preAuthorizeServer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinderClientTransport build() {
|
||||
return factoryBuilder
|
||||
.buildClientTransportFactory()
|
||||
.newClientTransport(serverAddress, new ClientTransportOptions(), null);
|
||||
|
@ -372,11 +378,12 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testBlackHoleSecurityPolicyConnectTimeout() throws Exception {
|
||||
public void testBlackHoleSecurityPolicyAuthTimeout() throws Exception {
|
||||
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||
transport =
|
||||
new BinderClientTransportBuilder()
|
||||
.setSecurityPolicy(securityPolicy)
|
||||
.setPreAuthorizeServer(false)
|
||||
.setReadyTimeoutMillis(1_234)
|
||||
.build();
|
||||
transport.start(transportListener).run();
|
||||
|
@ -387,15 +394,39 @@ public final class BinderClientTransportTest {
|
|||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncSecurityPolicyFailure() throws Exception {
|
||||
public void testBlackHoleSecurityPolicyPreAuthTimeout() throws Exception {
|
||||
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
|
||||
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);
|
||||
|
@ -406,15 +437,55 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncSecurityPolicySuccess() throws Exception {
|
||||
public void testAsyncSecurityPolicyPreAuthFailure() throws Exception {
|
||||
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
|
||||
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
|
||||
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);
|
||||
.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();
|
||||
}
|
||||
|
||||
|
@ -431,8 +502,7 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
private static void startAndAwaitReady(
|
||||
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
|
||||
throws Exception {
|
||||
BinderClientTransport transport, TestTransportListener transportListener) throws Exception {
|
||||
transport.start(transportListener).run();
|
||||
transportListener.awaitReady();
|
||||
}
|
||||
|
|
|
@ -106,8 +106,7 @@ public final class BinderTransportTest extends AbstractTransportTest {
|
|||
options.setEagAttributes(eagAttrs());
|
||||
options.setChannelLogger(transportLogger());
|
||||
|
||||
return new BinderTransport.BinderClientTransport(
|
||||
builder.buildClientTransportFactory(), addr, options);
|
||||
return new BinderClientTransport(builder.buildClientTransportFactory(), addr, options);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -58,7 +58,7 @@ public final class AndroidComponentAddress extends SocketAddress {
|
|||
@Nullable
|
||||
private final UserHandle targetUser; // null means the same user that hosts this process.
|
||||
|
||||
protected AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
|
||||
private AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
|
||||
checkArgument(
|
||||
bindIntent.getComponent() != null || bindIntent.getPackage() != null,
|
||||
"'bindIntent' must be explicit. Specify either a package or ComponentName.");
|
||||
|
@ -250,7 +250,22 @@ public final class AndroidComponentAddress extends SocketAddress {
|
|||
return this;
|
||||
}
|
||||
|
||||
/** See {@link AndroidComponentAddress#getTargetUser()}. */
|
||||
/**
|
||||
* Specifies the Android user in which the built Address' bind Intent will be evaluated.
|
||||
*
|
||||
* <p>Connecting to a server in a different Android user is uncommon and requires the client app
|
||||
* have runtime visibility of @SystemApi's and hold certain @SystemApi permissions.
|
||||
* The device must also be running Android SDK version 30 or higher.
|
||||
*
|
||||
* <p>See https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces
|
||||
* for details on which apps can call the underlying @SystemApi's needed to make this type
|
||||
* of connection.
|
||||
*
|
||||
* <p>One of the "android.permission.INTERACT_ACROSS_XXX" permissions is required. The exact one
|
||||
* depends on the calling user's relationship to the target user, whether client and server are
|
||||
* in the same or different apps, and the version of Android in use. See {@link
|
||||
* Context#bindServiceAsUser}, the essential underlying Android API, for details.
|
||||
*/
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
|
||||
public Builder setTargetUser(@Nullable UserHandle targetUser) {
|
||||
this.targetUser = targetUser;
|
||||
|
|
|
@ -18,6 +18,8 @@ package io.grpc.binder;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.EquivalentAddressGroup;
|
||||
import io.grpc.ExperimentalApi;
|
||||
import io.grpc.NameResolver;
|
||||
|
||||
|
@ -32,15 +34,42 @@ public final class ApiConstants {
|
|||
*/
|
||||
public static final String ACTION_BIND = "grpc.io.action.BIND";
|
||||
|
||||
/**
|
||||
* Gives a {@link NameResolver} access to its Channel's "source" {@link android.content.Context},
|
||||
* the entry point to almost every other Android API.
|
||||
*
|
||||
* <p>This argument is set automatically by {@link BinderChannelBuilder}. Any value passed to
|
||||
* {@link io.grpc.ManagedChannelBuilder#setNameResolverArg} will be ignored.
|
||||
*
|
||||
* <p>See {@link BinderChannelBuilder#forTarget(String, android.content.Context)} for more.
|
||||
*/
|
||||
public static final NameResolver.Args.Key<android.content.Context> SOURCE_ANDROID_CONTEXT =
|
||||
NameResolver.Args.Key.create("source-android-context");
|
||||
|
||||
/**
|
||||
* Specifies the Android user in which target URIs should be resolved.
|
||||
*
|
||||
* <p>{@link UserHandle} can't reasonably be encoded in a target URI string. Instead, all
|
||||
* {@link io.grpc.NameResolverProvider}s producing {@link AndroidComponentAddress}es should let
|
||||
* clients address servers in another Android user using this argument.
|
||||
* <p>{@link UserHandle} can't reasonably be encoded in a target URI string. Instead, all {@link
|
||||
* io.grpc.NameResolverProvider}s producing {@link AndroidComponentAddress}es should let clients
|
||||
* address servers in another Android user using this argument.
|
||||
*
|
||||
* <p>See also {@link AndroidComponentAddress#getTargetUser()}.
|
||||
* <p>Connecting to a server in a different Android user is uncommon and can only be done by a
|
||||
* "system app" client with special permissions. See {@link
|
||||
* AndroidComponentAddress.Builder#setTargetUser(UserHandle)} for details.
|
||||
*/
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
|
||||
public static final NameResolver.Args.Key<UserHandle> TARGET_ANDROID_USER =
|
||||
NameResolver.Args.Key.create("target-android-user");
|
||||
|
||||
/**
|
||||
* Lets you override a Channel's pre-auth configuration (see {@link
|
||||
* BinderChannelBuilder#preAuthorizeServers(boolean)}) for a given {@link EquivalentAddressGroup}.
|
||||
*
|
||||
* <p>A {@link NameResolver} that discovers servers from an untrusted source like PackageManager
|
||||
* can use this to force server pre-auth and prevent abuse.
|
||||
*/
|
||||
@EquivalentAddressGroup.Attr
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12191")
|
||||
public static final Attributes.Key<Boolean> PRE_AUTH_SERVER_OVERRIDE =
|
||||
Attributes.Key.create("pre-auth-server-override");
|
||||
}
|
||||
|
|
|
@ -67,4 +67,25 @@ public abstract class AsyncSecurityPolicy extends SecurityPolicy {
|
|||
* authorized.
|
||||
*/
|
||||
public abstract ListenableFuture<Status> checkAuthorizationAsync(int uid);
|
||||
|
||||
/**
|
||||
* Decides whether the given Android UID is authorized, without providing its raw integer value.
|
||||
*
|
||||
* <p>Calling this is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
|
||||
* the caller provides a {@link PeerUid} wrapper instead of the raw integer uid (known only to the
|
||||
* transport). This allows a server to check additional application-layer security policy for
|
||||
* itself *after* the call itself is authorized by the transport layer. Cross cutting application-
|
||||
* layer checks could be done from a {@link io.grpc.ServerInterceptor}. Checks based on the
|
||||
* substance of a request message could be done by the individual RPC method implementations
|
||||
* themselves.
|
||||
*
|
||||
* <p>See #checkAuthorizationAsync(int) for details on the semantics. See {@link
|
||||
* PeerUids#newPeerIdentifyingServerInterceptor()} for how to get a {@link PeerUid}.
|
||||
*
|
||||
* @param uid The Android UID to authenticate.
|
||||
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||
*/
|
||||
public final ListenableFuture<Status> checkAuthorizationAsync(PeerUid uid) {
|
||||
return checkAuthorizationAsync(uid.getUid());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -242,9 +242,9 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
|||
* specify a {@link UserHandle}. If neither the Channel nor the {@link AndroidComponentAddress}
|
||||
* specifies a target user, the {@link UserHandle} of the current process will be used.
|
||||
*
|
||||
* <p>Targeting a Service in a different Android user is uncommon and requires special permissions
|
||||
* normally reserved for system apps. See {@link android.content.Context#bindServiceAsUser} for
|
||||
* details.
|
||||
* <p>Connecting to a server in a different Android user is uncommon and can only be done by a
|
||||
* "system app" client with special permissions. See {@link
|
||||
* AndroidComponentAddress.Builder#setTargetUser(UserHandle)} for details.
|
||||
*
|
||||
* @deprecated This method's name is misleading because it implies an impersonated client identity
|
||||
* when it's actually specifying part of the server's location. It's also no longer necessary
|
||||
|
@ -279,6 +279,35 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks servers against this Channel's {@link SecurityPolicy} *before* binding.
|
||||
*
|
||||
* <p>Android users can be tricked into installing a malicious app with the same package name as a
|
||||
* legitimate server. That's why we don't send calls to a server until it has been authorized by
|
||||
* an appropriate {@link SecurityPolicy}. But merely binding to a malicious server can enable
|
||||
* "keep-alive" and "background activity launch" abuse, even if it's ultimately unauthorized.
|
||||
* Pre-authorization mitigates these threats by performing a preliminary {@link SecurityPolicy}
|
||||
* check against a server app's PackageManager-registered identity without actually creating an
|
||||
* instance of it. This is especially important for security when the server's direct address
|
||||
* isn't known in advance but rather resolved via target URI or discovered by other means.
|
||||
*
|
||||
* <p>Note that, unlike ordinary authorization, pre-authorization is performed against the server
|
||||
* app's UID, not the UID of the process hosting the bound Service. These can be different, most
|
||||
* commonly due to services that set `android:isolatedProcess=true`.
|
||||
*
|
||||
* <p>Pre-authorization is strongly recommended but it remains optional for now because of this
|
||||
* behavior change and the small performance cost.
|
||||
*
|
||||
* <p>The default value of this property is false but it will become true in a future release.
|
||||
* Clients that require a particular behavior should configure it explicitly using this method
|
||||
* rather than relying on the default.
|
||||
*/
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12191")
|
||||
public BinderChannelBuilder preAuthorizeServers(boolean preAuthorize) {
|
||||
transportFactoryBuilder.setPreAuthorizeServers(preAuthorize);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinderChannelBuilder idleTimeout(long value, TimeUnit unit) {
|
||||
checkState(
|
||||
|
@ -292,6 +321,8 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
|||
public ManagedChannel build() {
|
||||
transportFactoryBuilder.setOffloadExecutorPool(
|
||||
managedChannelImplBuilder.getOffloadExecutorPool());
|
||||
setNameResolverArg(
|
||||
ApiConstants.SOURCE_ANDROID_CONTEXT, transportFactoryBuilder.getSourceContext());
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,7 +184,6 @@ public final class SecurityPolicies {
|
|||
* Creates {@link SecurityPolicy} which checks if the app is a device owner app. See {@link
|
||||
* DevicePolicyManager}.
|
||||
*/
|
||||
@RequiresApi(18)
|
||||
public static io.grpc.binder.SecurityPolicy isDeviceOwner(Context applicationContext) {
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
|
@ -199,7 +198,6 @@ public final class SecurityPolicies {
|
|||
* Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See {@link
|
||||
* DevicePolicyManager}.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public static SecurityPolicy isProfileOwner(Context applicationContext) {
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
|
|
|
@ -53,4 +53,25 @@ public abstract class SecurityPolicy {
|
|||
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||
*/
|
||||
public abstract Status checkAuthorization(int uid);
|
||||
|
||||
/**
|
||||
* Decides whether the given Android UID is authorized, without providing its raw integer value.
|
||||
*
|
||||
* <p>Calling this is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
|
||||
* the caller provides a {@link PeerUid} wrapper instead of the raw integer uid (known only to the
|
||||
* transport). This allows a server to check additional application-layer security policy for
|
||||
* itself *after* the call itself is authorized by the transport layer. Cross cutting application-
|
||||
* layer checks could be done from a {@link io.grpc.ServerInterceptor}. Checks based on the
|
||||
* substance of a request message could be done by the individual RPC method implementations
|
||||
* themselves.
|
||||
*
|
||||
* <p>See #checkAuthorizationAsync(int) for details on the semantics. See {@link
|
||||
* PeerUids#newPeerIdentifyingServerInterceptor()} for how to get a {@link PeerUid}.
|
||||
*
|
||||
* @param uid The Android UID to authenticate.
|
||||
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||
*/
|
||||
public final Status checkAuthorization(PeerUid uid) {
|
||||
return checkAuthorization(uid.getUid());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ import io.grpc.internal.ServerTransport;
|
|||
import io.grpc.internal.ServerTransportListener;
|
||||
|
||||
/**
|
||||
* Tracks which {@link BinderTransport.BinderServerTransport} are currently active and allows
|
||||
* invoking a {@link Runnable} only once all transports are terminated.
|
||||
* Tracks which {@link BinderServerTransport} are currently active and allows invoking a {@link
|
||||
* Runnable} only once all transports are terminated.
|
||||
*/
|
||||
final class ActiveTransportTracker implements ServerListener {
|
||||
private final ServerListener delegate;
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.IBinder;
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
|
||||
/** An interface for managing a {@code Binder} connection. */
|
||||
interface Bindable {
|
||||
|
@ -45,6 +47,19 @@ interface Bindable {
|
|||
void onUnbound(Status reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches details about the remote Service from PackageManager without binding to it.
|
||||
*
|
||||
* <p>Resolving an untrusted address before binding to it lets you screen out problematic servers
|
||||
* before giving them a chance to run. However, note that the identity/existence of the resolved
|
||||
* Service can change between the time this method returns and the time you actually bind/connect
|
||||
* to it. For example, suppose the target package gets uninstalled or upgraded right after this
|
||||
* method returns. In {@link Observer#onBound}, you should verify that the server you resolved is
|
||||
* the same one you connected to.
|
||||
*/
|
||||
@AnyThread
|
||||
ServiceInfo resolve() throws StatusException;
|
||||
|
||||
/**
|
||||
* Attempt to bind with the remote service.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,441 @@
|
|||
/*
|
||||
* Copyright 2020 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static io.grpc.binder.ApiConstants.PRE_AUTH_SERVER_OVERRIDE;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Process;
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.errorprone.annotations.CheckReturnValue;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.ClientStreamTracer;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Internal;
|
||||
import io.grpc.InternalLogId;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.SecurityLevel;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import io.grpc.binder.AsyncSecurityPolicy;
|
||||
import io.grpc.binder.InboundParcelablePolicy;
|
||||
import io.grpc.binder.SecurityPolicy;
|
||||
import io.grpc.internal.ClientStream;
|
||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||
import io.grpc.internal.ConnectionClientTransport;
|
||||
import io.grpc.internal.FailingClientStream;
|
||||
import io.grpc.internal.GrpcAttributes;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.ManagedClientTransport;
|
||||
import io.grpc.internal.ObjectPool;
|
||||
import io.grpc.internal.StatsTraceContext;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/** Concrete client-side transport implementation. */
|
||||
@ThreadSafe
|
||||
@Internal
|
||||
public final class BinderClientTransport extends BinderTransport
|
||||
implements ConnectionClientTransport, Bindable.Observer {
|
||||
|
||||
private final ObjectPool<? extends Executor> offloadExecutorPool;
|
||||
private final Executor offloadExecutor;
|
||||
private final SecurityPolicy securityPolicy;
|
||||
private final Bindable serviceBinding;
|
||||
|
||||
/** Number of ongoing calls which keep this transport "in-use". */
|
||||
private final AtomicInteger numInUseStreams;
|
||||
|
||||
private final long readyTimeoutMillis;
|
||||
private final PingTracker pingTracker;
|
||||
private final boolean preAuthorizeServer;
|
||||
|
||||
@Nullable private ManagedClientTransport.Listener clientTransportListener;
|
||||
|
||||
@GuardedBy("this")
|
||||
private int latestCallId = FIRST_CALL_ID;
|
||||
|
||||
@GuardedBy("this")
|
||||
private ScheduledFuture<?> readyTimeoutFuture; // != null iff timeout scheduled.
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private ListenableFuture<Status> authResultFuture; // null before we check auth.
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private ListenableFuture<Status> preAuthResultFuture; // null before we pre-auth.
|
||||
|
||||
/**
|
||||
* Constructs a new transport instance.
|
||||
*
|
||||
* @param factory parameters common to all a Channel's transports
|
||||
* @param targetAddress the fully resolved and load-balanced server address
|
||||
* @param options other parameters that can vary as transports come and go within a Channel
|
||||
*/
|
||||
public BinderClientTransport(
|
||||
BinderClientTransportFactory factory,
|
||||
AndroidComponentAddress targetAddress,
|
||||
ClientTransportOptions options) {
|
||||
super(
|
||||
factory.scheduledExecutorPool,
|
||||
buildClientAttributes(
|
||||
options.getEagAttributes(),
|
||||
factory.sourceContext,
|
||||
targetAddress,
|
||||
factory.inboundParcelablePolicy),
|
||||
factory.binderDecorator,
|
||||
buildLogId(factory.sourceContext, targetAddress));
|
||||
this.offloadExecutorPool = factory.offloadExecutorPool;
|
||||
this.securityPolicy = factory.securityPolicy;
|
||||
this.offloadExecutor = offloadExecutorPool.getObject();
|
||||
this.readyTimeoutMillis = factory.readyTimeoutMillis;
|
||||
Boolean preAuthServerOverride = options.getEagAttributes().get(PRE_AUTH_SERVER_OVERRIDE);
|
||||
this.preAuthorizeServer =
|
||||
preAuthServerOverride != null ? preAuthServerOverride : factory.preAuthorizeServers;
|
||||
numInUseStreams = new AtomicInteger();
|
||||
pingTracker = new PingTracker(Ticker.systemTicker(), (id) -> sendPing(id));
|
||||
|
||||
serviceBinding =
|
||||
new ServiceBinding(
|
||||
factory.mainThreadExecutor,
|
||||
factory.sourceContext,
|
||||
factory.channelCredentials,
|
||||
targetAddress.asBindIntent(),
|
||||
targetAddress.getTargetUser() != null
|
||||
? targetAddress.getTargetUser()
|
||||
: factory.defaultTargetUserHandle,
|
||||
factory.bindServiceFlags.toInteger(),
|
||||
this);
|
||||
}
|
||||
|
||||
@Override
|
||||
void releaseExecutors() {
|
||||
super.releaseExecutors();
|
||||
offloadExecutorPool.returnObject(offloadExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onBound(IBinder binder) {
|
||||
sendSetupTransaction(binderDecorator.decorate(OneWayBinderProxy.wrap(binder, offloadExecutor)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onUnbound(Status reason) {
|
||||
shutdownInternal(reason, true);
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
@Override
|
||||
public synchronized Runnable start(Listener clientTransportListener) {
|
||||
this.clientTransportListener = checkNotNull(clientTransportListener);
|
||||
return () -> {
|
||||
synchronized (BinderClientTransport.this) {
|
||||
if (inState(TransportState.NOT_STARTED)) {
|
||||
setState(TransportState.SETUP);
|
||||
try {
|
||||
if (preAuthorizeServer) {
|
||||
preAuthorize(serviceBinding.resolve());
|
||||
} else {
|
||||
serviceBinding.bind();
|
||||
}
|
||||
} catch (StatusException e) {
|
||||
shutdownInternal(e.getStatus(), true);
|
||||
return;
|
||||
}
|
||||
if (readyTimeoutMillis >= 0) {
|
||||
readyTimeoutFuture =
|
||||
getScheduledExecutorService()
|
||||
.schedule(
|
||||
BinderClientTransport.this::onReadyTimeout,
|
||||
readyTimeoutMillis,
|
||||
MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void preAuthorize(ServiceInfo serviceInfo) {
|
||||
// It's unlikely, but the identity/existence of this Service could change by the time we
|
||||
// actually connect. It doesn't matter though, because:
|
||||
// - If pre-auth fails (but would succeed against the server's new state), the grpc-core layer
|
||||
// will eventually retry using a new transport instance that will see the Service's new state.
|
||||
// - If pre-auth succeeds (but would fail against the server's new state), we might give an
|
||||
// unauthorized server a chance to run, but the connection will still fail by SecurityPolicy
|
||||
// check later in handshake. Pre-auth remains effective at mitigating abuse because malware
|
||||
// can't typically control the exact timing of its installation.
|
||||
preAuthResultFuture = checkServerAuthorizationAsync(serviceInfo.applicationInfo.uid);
|
||||
Futures.addCallback(
|
||||
preAuthResultFuture,
|
||||
new FutureCallback<Status>() {
|
||||
@Override
|
||||
public void onSuccess(Status result) {
|
||||
handlePreAuthResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
handleAuthResult(t);
|
||||
}
|
||||
},
|
||||
offloadExecutor);
|
||||
}
|
||||
|
||||
private synchronized void handlePreAuthResult(Status authorization) {
|
||||
if (inState(TransportState.SETUP)) {
|
||||
if (!authorization.isOk()) {
|
||||
shutdownInternal(authorization, true);
|
||||
} else {
|
||||
serviceBinding.bind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onReadyTimeout() {
|
||||
if (inState(TransportState.SETUP)) {
|
||||
readyTimeoutFuture = null;
|
||||
shutdownInternal(
|
||||
Status.DEADLINE_EXCEEDED.withDescription(
|
||||
"Connect timeout " + readyTimeoutMillis + "ms lapsed"),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ClientStream newStream(
|
||||
final MethodDescriptor<?, ?> method,
|
||||
final Metadata headers,
|
||||
final CallOptions callOptions,
|
||||
ClientStreamTracer[] tracers) {
|
||||
if (!inState(TransportState.READY)) {
|
||||
return newFailingClientStream(
|
||||
isShutdown()
|
||||
? shutdownStatus
|
||||
: Status.INTERNAL.withDescription("newStream() before transportReady()"),
|
||||
attributes,
|
||||
headers,
|
||||
tracers);
|
||||
}
|
||||
|
||||
int callId = latestCallId++;
|
||||
if (latestCallId == LAST_CALL_ID) {
|
||||
latestCallId = FIRST_CALL_ID;
|
||||
}
|
||||
StatsTraceContext statsTraceContext =
|
||||
StatsTraceContext.newClientContext(tracers, attributes, headers);
|
||||
Inbound.ClientInbound inbound =
|
||||
new Inbound.ClientInbound(
|
||||
this, attributes, callId, GrpcUtil.shouldBeCountedForInUse(callOptions));
|
||||
if (ongoingCalls.putIfAbsent(callId, inbound) != null) {
|
||||
Status failure = Status.INTERNAL.withDescription("Clashing call IDs");
|
||||
shutdownInternal(failure, true);
|
||||
return newFailingClientStream(failure, attributes, headers, tracers);
|
||||
} else {
|
||||
if (inbound.countsForInUse() && numInUseStreams.getAndIncrement() == 0) {
|
||||
clientTransportListener.transportInUse(true);
|
||||
}
|
||||
Outbound.ClientOutbound outbound =
|
||||
new Outbound.ClientOutbound(this, callId, method, headers, statsTraceContext);
|
||||
if (method.getType().clientSendsOneMessage()) {
|
||||
return new SingleMessageClientStream(inbound, outbound, attributes);
|
||||
} else {
|
||||
return new MultiMessageClientStream(inbound, outbound, attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterInbound(Inbound<?> inbound) {
|
||||
if (inbound.countsForInUse() && numInUseStreams.decrementAndGet() == 0) {
|
||||
clientTransportListener.transportInUse(false);
|
||||
}
|
||||
super.unregisterInbound(inbound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ping(final PingCallback callback, Executor executor) {
|
||||
pingTracker.startPing(callback, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdown(Status reason) {
|
||||
checkNotNull(reason, "reason");
|
||||
shutdownInternal(reason, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdownNow(Status reason) {
|
||||
checkNotNull(reason, "reason");
|
||||
shutdownInternal(reason, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
void notifyShutdown(Status status) {
|
||||
clientTransportListener.transportShutdown(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
void notifyTerminated() {
|
||||
if (numInUseStreams.getAndSet(0) > 0) {
|
||||
clientTransportListener.transportInUse(false);
|
||||
}
|
||||
if (readyTimeoutFuture != null) {
|
||||
readyTimeoutFuture.cancel(false);
|
||||
readyTimeoutFuture = null;
|
||||
}
|
||||
if (preAuthResultFuture != null) {
|
||||
preAuthResultFuture.cancel(false); // No effect if already complete.
|
||||
}
|
||||
if (authResultFuture != null) {
|
||||
authResultFuture.cancel(false); // No effect if already complete.
|
||||
}
|
||||
serviceBinding.unbind();
|
||||
clientTransportListener.transportTerminated();
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
protected void handleSetupTransport(Parcel parcel) {
|
||||
int remoteUid = Binder.getCallingUid();
|
||||
attributes = setSecurityAttrs(attributes, remoteUid);
|
||||
if (inState(TransportState.SETUP)) {
|
||||
int version = parcel.readInt();
|
||||
IBinder binder = parcel.readStrongBinder();
|
||||
if (version != WIRE_FORMAT_VERSION) {
|
||||
shutdownInternal(Status.UNAVAILABLE.withDescription("Wire format version mismatch"), true);
|
||||
} else if (binder == null) {
|
||||
shutdownInternal(
|
||||
Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true);
|
||||
} else {
|
||||
authResultFuture = checkServerAuthorizationAsync(remoteUid);
|
||||
Futures.addCallback(
|
||||
authResultFuture,
|
||||
new FutureCallback<Status>() {
|
||||
@Override
|
||||
public void onSuccess(Status result) {
|
||||
handleAuthResult(binder, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
handleAuthResult(t);
|
||||
}
|
||||
},
|
||||
offloadExecutor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ListenableFuture<Status> checkServerAuthorizationAsync(int remoteUid) {
|
||||
return (securityPolicy instanceof AsyncSecurityPolicy)
|
||||
? ((AsyncSecurityPolicy) securityPolicy).checkAuthorizationAsync(remoteUid)
|
||||
: Futures.submit(() -> securityPolicy.checkAuthorization(remoteUid), offloadExecutor);
|
||||
}
|
||||
|
||||
private synchronized void handleAuthResult(IBinder binder, Status authorization) {
|
||||
if (inState(TransportState.SETUP)) {
|
||||
if (!authorization.isOk()) {
|
||||
shutdownInternal(authorization, true);
|
||||
} else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) {
|
||||
shutdownInternal(
|
||||
Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true);
|
||||
} else {
|
||||
// Check state again, since a failure inside setOutgoingBinder (or a callback it
|
||||
// triggers), could have shut us down.
|
||||
if (!isShutdown()) {
|
||||
setState(TransportState.READY);
|
||||
attributes = clientTransportListener.filterTransport(attributes);
|
||||
clientTransportListener.transportReady();
|
||||
if (readyTimeoutFuture != null) {
|
||||
readyTimeoutFuture.cancel(false);
|
||||
readyTimeoutFuture = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void handleAuthResult(Throwable t) {
|
||||
shutdownInternal(
|
||||
Status.INTERNAL.withDescription("Could not evaluate SecurityPolicy").withCause(t), true);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
@Override
|
||||
protected void handlePingResponse(Parcel parcel) {
|
||||
pingTracker.onPingResponse(parcel.readInt());
|
||||
}
|
||||
|
||||
private static ClientStream newFailingClientStream(
|
||||
Status failure, Attributes attributes, Metadata headers, ClientStreamTracer[] tracers) {
|
||||
StatsTraceContext statsTraceContext =
|
||||
StatsTraceContext.newClientContext(tracers, attributes, headers);
|
||||
statsTraceContext.clientOutboundHeaders();
|
||||
return new FailingClientStream(failure, tracers);
|
||||
}
|
||||
|
||||
private static InternalLogId buildLogId(
|
||||
Context sourceContext, AndroidComponentAddress targetAddress) {
|
||||
return InternalLogId.allocate(
|
||||
BinderClientTransport.class,
|
||||
sourceContext.getClass().getSimpleName() + "->" + targetAddress);
|
||||
}
|
||||
|
||||
private static Attributes buildClientAttributes(
|
||||
Attributes eagAttrs,
|
||||
Context sourceContext,
|
||||
AndroidComponentAddress targetAddress,
|
||||
InboundParcelablePolicy inboundParcelablePolicy) {
|
||||
return Attributes.newBuilder()
|
||||
.set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) // Trust noone for now.
|
||||
.set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs)
|
||||
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, AndroidComponentAddress.forContext(sourceContext))
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, targetAddress)
|
||||
.set(INBOUND_PARCELABLE_POLICY, inboundParcelablePolicy)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Attributes setSecurityAttrs(Attributes attributes, int uid) {
|
||||
return attributes.toBuilder()
|
||||
.set(REMOTE_UID, uid)
|
||||
.set(
|
||||
GrpcAttributes.ATTR_SECURITY_LEVEL,
|
||||
uid == Process.myUid()
|
||||
? SecurityLevel.PRIVACY_AND_INTEGRITY
|
||||
: SecurityLevel.INTEGRITY) // TODO: Have the SecrityPolicy decide this.
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
final InboundParcelablePolicy inboundParcelablePolicy;
|
||||
final OneWayBinderProxy.Decorator binderDecorator;
|
||||
final long readyTimeoutMillis;
|
||||
final boolean preAuthorizeServers; // TODO(jdcormie): Default to true.
|
||||
|
||||
ScheduledExecutorService executorService;
|
||||
Executor offloadExecutor;
|
||||
|
@ -75,18 +76,19 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
inboundParcelablePolicy = checkNotNull(builder.inboundParcelablePolicy);
|
||||
binderDecorator = checkNotNull(builder.binderDecorator);
|
||||
readyTimeoutMillis = builder.readyTimeoutMillis;
|
||||
preAuthorizeServers = builder.preAuthorizeServers;
|
||||
|
||||
executorService = scheduledExecutorPool.getObject();
|
||||
offloadExecutor = offloadExecutorPool.getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinderTransport.BinderClientTransport newClientTransport(
|
||||
public BinderClientTransport newClientTransport(
|
||||
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
|
||||
if (closed) {
|
||||
throw new IllegalStateException("The transport factory is closed.");
|
||||
}
|
||||
return new BinderTransport.BinderClientTransport(this, (AndroidComponentAddress) addr, options);
|
||||
return new BinderClientTransport(this, (AndroidComponentAddress) addr, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -128,6 +130,7 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
InboundParcelablePolicy inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT;
|
||||
OneWayBinderProxy.Decorator binderDecorator = OneWayBinderProxy.IDENTITY_DECORATOR;
|
||||
long readyTimeoutMillis = 60_000;
|
||||
boolean preAuthorizeServers;
|
||||
|
||||
@Override
|
||||
public BinderClientTransportFactory buildClientTransportFactory() {
|
||||
|
@ -139,6 +142,10 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
return this;
|
||||
}
|
||||
|
||||
public Context getSourceContext() {
|
||||
return sourceContext;
|
||||
}
|
||||
|
||||
public Builder setOffloadExecutorPool(ObjectPool<? extends Executor> offloadExecutorPool) {
|
||||
this.offloadExecutorPool = checkNotNull(offloadExecutorPool, "offloadExecutorPool");
|
||||
return this;
|
||||
|
@ -216,5 +223,11 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
this.readyTimeoutMillis = readyTimeoutMillis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Whether to check server addresses against the SecurityPolicy *before* binding to them. */
|
||||
public Builder setPreAuthorizeServers(boolean preAuthorizeServers) {
|
||||
this.preAuthorizeServers = preAuthorizeServers;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,8 +178,8 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder.
|
|||
serverPolicyChecker,
|
||||
checkNotNull(executor, "Not started?"));
|
||||
// Create a new transport and let our listener know about it.
|
||||
BinderTransport.BinderServerTransport transport =
|
||||
new BinderTransport.BinderServerTransport(
|
||||
BinderServerTransport transport =
|
||||
new BinderServerTransport(
|
||||
executorServicePool,
|
||||
attrsBuilder.build(),
|
||||
streamTracerFactories,
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2020 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import android.os.IBinder;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Internal;
|
||||
import io.grpc.InternalLogId;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerStreamTracer;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.internal.ObjectPool;
|
||||
import io.grpc.internal.ServerStream;
|
||||
import io.grpc.internal.ServerTransport;
|
||||
import io.grpc.internal.ServerTransportListener;
|
||||
import io.grpc.internal.StatsTraceContext;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Concrete server-side transport implementation. */
|
||||
@Internal
|
||||
public final class BinderServerTransport extends BinderTransport implements ServerTransport {
|
||||
|
||||
private final List<ServerStreamTracer.Factory> streamTracerFactories;
|
||||
@Nullable private ServerTransportListener serverTransportListener;
|
||||
|
||||
/**
|
||||
* Constructs a new transport instance.
|
||||
*
|
||||
* @param binderDecorator used to decorate 'callbackBinder', for fault injection.
|
||||
*/
|
||||
public BinderServerTransport(
|
||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||
Attributes attributes,
|
||||
List<ServerStreamTracer.Factory> streamTracerFactories,
|
||||
OneWayBinderProxy.Decorator binderDecorator,
|
||||
IBinder callbackBinder) {
|
||||
super(executorServicePool, attributes, binderDecorator, buildLogId(attributes));
|
||||
this.streamTracerFactories = streamTracerFactories;
|
||||
// TODO(jdcormie): Plumb in the Server's executor() and use it here instead.
|
||||
setOutgoingBinder(OneWayBinderProxy.wrap(callbackBinder, getScheduledExecutorService()));
|
||||
}
|
||||
|
||||
public synchronized void setServerTransportListener(
|
||||
ServerTransportListener serverTransportListener) {
|
||||
this.serverTransportListener = serverTransportListener;
|
||||
if (isShutdown()) {
|
||||
setState(TransportState.SHUTDOWN_TERMINATED);
|
||||
notifyTerminated();
|
||||
releaseExecutors();
|
||||
} else {
|
||||
sendSetupTransaction();
|
||||
// Check we're not shutdown again, since a failure inside sendSetupTransaction (or a callback
|
||||
// it triggers), could have shut us down.
|
||||
if (!isShutdown()) {
|
||||
setState(TransportState.READY);
|
||||
attributes = serverTransportListener.transportReady(attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatsTraceContext createStatsTraceContext(String methodName, Metadata headers) {
|
||||
return StatsTraceContext.newServerContext(streamTracerFactories, methodName, headers);
|
||||
}
|
||||
|
||||
synchronized Status startStream(ServerStream stream, String methodName, Metadata headers) {
|
||||
if (isShutdown()) {
|
||||
return Status.UNAVAILABLE.withDescription("transport is shutdown");
|
||||
} else {
|
||||
serverTransportListener.streamCreated(stream, methodName, headers);
|
||||
return Status.OK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
void notifyShutdown(Status status) {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
void notifyTerminated() {
|
||||
if (serverTransportListener != null) {
|
||||
serverTransportListener.transportTerminated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdown() {
|
||||
shutdownInternal(Status.OK, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdownNow(Status reason) {
|
||||
shutdownInternal(reason, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@GuardedBy("this")
|
||||
protected Inbound<?> createInbound(int callId) {
|
||||
return new Inbound.ServerInbound(this, attributes, callId);
|
||||
}
|
||||
|
||||
private static InternalLogId buildLogId(Attributes attributes) {
|
||||
return InternalLogId.allocate(
|
||||
BinderServerTransport.class, "from " + attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
|
||||
}
|
||||
}
|
|
@ -19,66 +19,32 @@ package io.grpc.binder.internal;
|
|||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.DeadObjectException;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.TransactionTooLargeException;
|
||||
import androidx.annotation.BinderThread;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.errorprone.annotations.CheckReturnValue;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.ClientStreamTracer;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Internal;
|
||||
import io.grpc.InternalChannelz.SocketStats;
|
||||
import io.grpc.InternalLogId;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.SecurityLevel;
|
||||
import io.grpc.ServerStreamTracer;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import io.grpc.binder.AsyncSecurityPolicy;
|
||||
import io.grpc.binder.InboundParcelablePolicy;
|
||||
import io.grpc.binder.SecurityPolicy;
|
||||
import io.grpc.internal.ClientStream;
|
||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||
import io.grpc.internal.ConnectionClientTransport;
|
||||
import io.grpc.internal.FailingClientStream;
|
||||
import io.grpc.internal.GrpcAttributes;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.ManagedClientTransport;
|
||||
import io.grpc.internal.ObjectPool;
|
||||
import io.grpc.internal.ServerStream;
|
||||
import io.grpc.internal.ServerTransport;
|
||||
import io.grpc.internal.ServerTransportListener;
|
||||
import io.grpc.internal.StatsTraceContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -168,10 +134,10 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
|
|||
private static final int RESERVED_TRANSACTIONS = 1000;
|
||||
|
||||
/** The first call ID we can use. */
|
||||
private static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
|
||||
static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
|
||||
|
||||
/** The last call ID we can use. */
|
||||
private static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
|
||||
static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
|
||||
|
||||
/** The states of this transport. */
|
||||
protected enum TransportState {
|
||||
|
@ -217,7 +183,7 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
|
|||
// Only read/written on @BinderThread.
|
||||
private long acknowledgedIncomingBytes;
|
||||
|
||||
private BinderTransport(
|
||||
protected BinderTransport(
|
||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||
Attributes attributes,
|
||||
OneWayBinderProxy.Decorator binderDecorator,
|
||||
|
@ -558,419 +524,6 @@ 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.
|
||||
@GuardedBy("this")
|
||||
@Nullable private ListenableFuture<Status> authResultFuture; // null before we check 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;
|
||||
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;
|
||||
}
|
||||
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 =
|
||||
(securityPolicy instanceof AsyncSecurityPolicy)
|
||||
? ((AsyncSecurityPolicy) securityPolicy).checkAuthorizationAsync(remoteUid)
|
||||
: Futures.submit(
|
||||
() -> securityPolicy.checkAuthorization(remoteUid), offloadExecutor);
|
||||
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 synchronized void handleAuthResult(IBinder binder, Status authorization) {
|
||||
if (inState(TransportState.SETUP)) {
|
||||
if (!authorization.isOk()) {
|
||||
shutdownInternal(authorization, true);
|
||||
} else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) {
|
||||
shutdownInternal(
|
||||
Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true);
|
||||
} else {
|
||||
// Check state again, since a failure inside setOutgoingBinder (or a callback it
|
||||
// triggers), could have shut us down.
|
||||
if (!isShutdown()) {
|
||||
setState(TransportState.READY);
|
||||
attributes = clientTransportListener.filterTransport(attributes);
|
||||
clientTransportListener.transportReady();
|
||||
if (readyTimeoutFuture != null) {
|
||||
readyTimeoutFuture.cancel(false);
|
||||
readyTimeoutFuture = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void handleAuthResult(Throwable t) {
|
||||
shutdownInternal(
|
||||
Status.INTERNAL.withDescription("Could not evaluate SecurityPolicy").withCause(t), true);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
@Override
|
||||
protected void handlePingResponse(Parcel parcel) {
|
||||
pingTracker.onPingResponse(parcel.readInt());
|
||||
}
|
||||
|
||||
private static ClientStream newFailingClientStream(
|
||||
Status failure, Attributes attributes, Metadata headers, ClientStreamTracer[] tracers) {
|
||||
StatsTraceContext statsTraceContext =
|
||||
StatsTraceContext.newClientContext(tracers, attributes, headers);
|
||||
statsTraceContext.clientOutboundHeaders();
|
||||
return new FailingClientStream(failure, tracers);
|
||||
}
|
||||
|
||||
private static InternalLogId buildLogId(
|
||||
Context sourceContext, AndroidComponentAddress targetAddress) {
|
||||
return InternalLogId.allocate(
|
||||
BinderClientTransport.class,
|
||||
sourceContext.getClass().getSimpleName() + "->" + targetAddress);
|
||||
}
|
||||
|
||||
private static Attributes buildClientAttributes(
|
||||
Attributes eagAttrs,
|
||||
Context sourceContext,
|
||||
AndroidComponentAddress targetAddress,
|
||||
InboundParcelablePolicy inboundParcelablePolicy) {
|
||||
return Attributes.newBuilder()
|
||||
.set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) // Trust noone for now.
|
||||
.set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs)
|
||||
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, AndroidComponentAddress.forContext(sourceContext))
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, targetAddress)
|
||||
.set(INBOUND_PARCELABLE_POLICY, inboundParcelablePolicy)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Attributes setSecurityAttrs(Attributes attributes, int uid) {
|
||||
return attributes.toBuilder()
|
||||
.set(REMOTE_UID, uid)
|
||||
.set(
|
||||
GrpcAttributes.ATTR_SECURITY_LEVEL,
|
||||
uid == Process.myUid()
|
||||
? SecurityLevel.PRIVACY_AND_INTEGRITY
|
||||
: SecurityLevel.INTEGRITY) // TODO: Have the SecrityPolicy decide this.
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Concrete server-side transport implementation. */
|
||||
@Internal
|
||||
public static final class BinderServerTransport extends BinderTransport
|
||||
implements ServerTransport {
|
||||
|
||||
private final List<ServerStreamTracer.Factory> streamTracerFactories;
|
||||
@Nullable private ServerTransportListener serverTransportListener;
|
||||
|
||||
/**
|
||||
* Constructs a new transport instance.
|
||||
*
|
||||
* @param binderDecorator used to decorate 'callbackBinder', for fault injection.
|
||||
*/
|
||||
public BinderServerTransport(
|
||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||
Attributes attributes,
|
||||
List<ServerStreamTracer.Factory> streamTracerFactories,
|
||||
OneWayBinderProxy.Decorator binderDecorator,
|
||||
IBinder callbackBinder) {
|
||||
super(executorServicePool, attributes, binderDecorator, buildLogId(attributes));
|
||||
this.streamTracerFactories = streamTracerFactories;
|
||||
// TODO(jdcormie): Plumb in the Server's executor() and use it here instead.
|
||||
setOutgoingBinder(OneWayBinderProxy.wrap(callbackBinder, getScheduledExecutorService()));
|
||||
}
|
||||
|
||||
public synchronized void setServerTransportListener(
|
||||
ServerTransportListener serverTransportListener) {
|
||||
this.serverTransportListener = serverTransportListener;
|
||||
if (isShutdown()) {
|
||||
setState(TransportState.SHUTDOWN_TERMINATED);
|
||||
notifyTerminated();
|
||||
releaseExecutors();
|
||||
} else {
|
||||
sendSetupTransaction();
|
||||
// Check we're not shutdown again, since a failure inside sendSetupTransaction (or a
|
||||
// callback it triggers), could have shut us down.
|
||||
if (!isShutdown()) {
|
||||
setState(TransportState.READY);
|
||||
attributes = serverTransportListener.transportReady(attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatsTraceContext createStatsTraceContext(String methodName, Metadata headers) {
|
||||
return StatsTraceContext.newServerContext(streamTracerFactories, methodName, headers);
|
||||
}
|
||||
|
||||
synchronized Status startStream(ServerStream stream, String methodName, Metadata headers) {
|
||||
if (isShutdown()) {
|
||||
return Status.UNAVAILABLE.withDescription("transport is shutdown");
|
||||
} else {
|
||||
serverTransportListener.streamCreated(stream, methodName, headers);
|
||||
return Status.OK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
void notifyShutdown(Status status) {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
void notifyTerminated() {
|
||||
if (serverTransportListener != null) {
|
||||
serverTransportListener.transportTerminated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdown() {
|
||||
shutdownInternal(Status.OK, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdownNow(Status reason) {
|
||||
shutdownInternal(reason, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@GuardedBy("this")
|
||||
protected Inbound<?> createInbound(int callId) {
|
||||
return new Inbound.ServerInbound(this, attributes, callId);
|
||||
}
|
||||
|
||||
private static InternalLogId buildLogId(Attributes attributes) {
|
||||
return InternalLogId.allocate(
|
||||
BinderServerTransport.class, "from " + attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkTransition(TransportState current, TransportState next) {
|
||||
switch (next) {
|
||||
case SETUP:
|
||||
|
|
|
@ -610,10 +610,9 @@ abstract class Inbound<L extends StreamListener> implements StreamListener.Messa
|
|||
// Server-side inbound transactions.
|
||||
static final class ServerInbound extends Inbound<ServerStreamListener> {
|
||||
|
||||
private final BinderTransport.BinderServerTransport serverTransport;
|
||||
private final BinderServerTransport serverTransport;
|
||||
|
||||
ServerInbound(
|
||||
BinderTransport.BinderServerTransport transport, Attributes attributes, int callId) {
|
||||
ServerInbound(BinderServerTransport transport, Attributes attributes, int callId) {
|
||||
super(transport, attributes, callId);
|
||||
this.serverTransport = transport;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static io.grpc.binder.internal.SystemApis.createContextAsUser;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Build;
|
||||
import android.os.UserHandle;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.EquivalentAddressGroup;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.StatusOr;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import io.grpc.binder.ApiConstants;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link NameResolver} that resolves Android-standard "intent:" target URIs to the list of {@link
|
||||
* AndroidComponentAddress} that match it by manifest intent filter.
|
||||
*/
|
||||
final class IntentNameResolver extends NameResolver {
|
||||
private final Intent targetIntent; // Never mutated.
|
||||
@Nullable private final UserHandle targetUser; // null means same user that hosts this process.
|
||||
private final Context targetUserContext;
|
||||
private final Executor offloadExecutor;
|
||||
private final Executor sequentialExecutor;
|
||||
private final SynchronizationContext syncContext;
|
||||
private final ServiceConfigParser serviceConfigParser;
|
||||
|
||||
// Accessed only on `sequentialExecutor`
|
||||
@Nullable private PackageChangeReceiver receiver; // != null when registered
|
||||
|
||||
// Accessed only on 'syncContext'.
|
||||
private boolean shutdown;
|
||||
private boolean queryNeeded;
|
||||
@Nullable private Listener2 listener; // != null after start().
|
||||
@Nullable private ListenableFuture<ResolutionResult> queryResultFuture; // != null when querying.
|
||||
|
||||
@EquivalentAddressGroup.Attr
|
||||
private static final Attributes CONSTANT_EAG_ATTRS =
|
||||
Attributes.newBuilder()
|
||||
// Servers discovered in PackageManager are especially untrusted. After all, any app can
|
||||
// declare any intent filter it wants! Require pre-authorization so that unauthorized apps
|
||||
// don't even get a chance to run onCreate()/onBind().
|
||||
.set(ApiConstants.PRE_AUTH_SERVER_OVERRIDE, true)
|
||||
.build();
|
||||
|
||||
IntentNameResolver(Intent targetIntent, Args args) {
|
||||
this.targetIntent = targetIntent;
|
||||
this.targetUser = args.getArg(ApiConstants.TARGET_ANDROID_USER);
|
||||
Context context =
|
||||
checkNotNull(args.getArg(ApiConstants.SOURCE_ANDROID_CONTEXT), "SOURCE_ANDROID_CONTEXT")
|
||||
.getApplicationContext();
|
||||
this.targetUserContext =
|
||||
targetUser != null ? createContextForTargetUserOrThrow(context, targetUser) : context;
|
||||
// This Executor is nominally optional but all grpc-java Channels provide it since 1.25.
|
||||
this.offloadExecutor =
|
||||
checkNotNull(args.getOffloadExecutor(), "NameResolver.Args.getOffloadExecutor()");
|
||||
// Ensures start()'s work runs before resolve()'s' work, and both run before shutdown()'s.
|
||||
this.sequentialExecutor = MoreExecutors.newSequentialExecutor(offloadExecutor);
|
||||
this.syncContext = args.getSynchronizationContext();
|
||||
this.serviceConfigParser = args.getServiceConfigParser();
|
||||
}
|
||||
|
||||
private static Context createContextForTargetUserOrThrow(Context context, UserHandle targetUser) {
|
||||
try {
|
||||
return createContextAsUser(context, targetUser, /* flags= */ 0); // @SystemApi since R.
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R and @SystemApi visibility");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Listener2 listener) {
|
||||
checkState(this.listener == null, "Already started!");
|
||||
checkState(!shutdown, "Resolver is shutdown");
|
||||
this.listener = checkNotNull(listener);
|
||||
sequentialExecutor.execute(this::registerReceiver);
|
||||
resolve();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
checkState(listener != null, "Not started!");
|
||||
resolve();
|
||||
}
|
||||
|
||||
private void resolve() {
|
||||
syncContext.throwIfNotInThisSynchronizationContext();
|
||||
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't block here in 'syncContext' so we offload PackageManager queries to an Executor.
|
||||
// But offloading complicates things a bit because other calls can arrive while we wait for the
|
||||
// results. We keep 'listener' up-to-date with the latest state in PackageManager by doing:
|
||||
// 1. Only one query-and-report-to-listener operation at a time.
|
||||
// 2. At least one query-and-report-to-listener AFTER every PackageManager state change.
|
||||
if (queryResultFuture == null) {
|
||||
queryResultFuture = Futures.submit(this::queryPackageManager, sequentialExecutor);
|
||||
queryResultFuture.addListener(this::onQueryComplete, syncContext);
|
||||
} else {
|
||||
// There's already a query in-flight but (2) says we need at least one more. Our sequential
|
||||
// Executor would be enough to ensure (1) but we also don't want a backlog of work to build up
|
||||
// if things change rapidly. Just make a note to start a new query when this one finishes.
|
||||
queryNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void onQueryComplete() {
|
||||
syncContext.throwIfNotInThisSynchronizationContext();
|
||||
checkState(queryResultFuture != null);
|
||||
checkState(queryResultFuture.isDone());
|
||||
|
||||
// Capture non-final `listener` here while we're on 'syncContext'.
|
||||
Listener2 listener = checkNotNull(this.listener);
|
||||
Futures.addCallback(
|
||||
queryResultFuture, // Already isDone() so this execute()s immediately.
|
||||
new FutureCallback<ResolutionResult>() {
|
||||
@Override
|
||||
public void onSuccess(ResolutionResult result) {
|
||||
listener.onResult2(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
listener.onResult2(
|
||||
ResolutionResult.newBuilder()
|
||||
.setAddressesOrError(StatusOr.fromStatus(Status.fromThrowable(t)))
|
||||
.build());
|
||||
}
|
||||
},
|
||||
syncContext); // Already on 'syncContext' but addCallback() is faster than try/get/catch.
|
||||
queryResultFuture = null;
|
||||
|
||||
if (queryNeeded) {
|
||||
// One or more resolve() requests arrived while we were working on the last one. Just one
|
||||
// follow-on query can subsume all of them.
|
||||
queryNeeded = false;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAuthority() {
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
syncContext.throwIfNotInThisSynchronizationContext();
|
||||
if (!shutdown) {
|
||||
shutdown = true;
|
||||
sequentialExecutor.execute(this::maybeUnregisterReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
private ResolutionResult queryPackageManager() throws StatusException {
|
||||
List<ResolveInfo> queryResults = queryIntentServices(targetIntent);
|
||||
|
||||
// Avoid a spurious UnsafeIntentLaunchViolation later. Since S, Android's StrictMode is very
|
||||
// conservative, marking any Intent parsed from a string as suspicious and complaining when you
|
||||
// bind to it. But all this is pointless with grpc-binder, which already goes even further by
|
||||
// not trusting addresses at all! Instead, we rely on SecurityPolicy, which won't allow a
|
||||
// connection to an unauthorized server UID no matter how you got there.
|
||||
Intent prototypeBindIntent = sanitize(targetIntent);
|
||||
|
||||
// Model each matching android.app.Service as an EAG (server) with a single address.
|
||||
List<EquivalentAddressGroup> addresses = new ArrayList<>();
|
||||
for (ResolveInfo resolveInfo : queryResults) {
|
||||
prototypeBindIntent.setComponent(
|
||||
new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name));
|
||||
addresses.add(
|
||||
new EquivalentAddressGroup(
|
||||
AndroidComponentAddress.newBuilder()
|
||||
.setBindIntent(prototypeBindIntent) // Makes a copy.
|
||||
.setTargetUser(targetUser)
|
||||
.build(),
|
||||
CONSTANT_EAG_ATTRS));
|
||||
}
|
||||
|
||||
return ResolutionResult.newBuilder()
|
||||
.setAddressesOrError(StatusOr.fromValue(addresses))
|
||||
// Empty service config means we get the default 'pick_first' load balancing policy.
|
||||
.setServiceConfig(serviceConfigParser.parseServiceConfig(ImmutableMap.of()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private List<ResolveInfo> queryIntentServices(Intent intent) throws StatusException {
|
||||
int flags = 0;
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
// Don't match direct-boot-unaware Services that can't presently be created. We'll query again
|
||||
// after the user is unlocked. The MATCH_DIRECT_BOOT_AUTO behavior is actually the default but
|
||||
// being explicit here avoids an android.os.strictmode.ImplicitDirectBootViolation.
|
||||
flags |= PackageManager.MATCH_DIRECT_BOOT_AUTO;
|
||||
}
|
||||
|
||||
List<ResolveInfo> intentServices =
|
||||
targetUserContext.getPackageManager().queryIntentServices(intent, flags);
|
||||
if (intentServices == null || intentServices.isEmpty()) {
|
||||
// Must be the same as when ServiceBinding's call to bindService() returns false.
|
||||
throw Status.UNIMPLEMENTED
|
||||
.withDescription("Service not found for intent " + intent)
|
||||
.asException();
|
||||
}
|
||||
return intentServices;
|
||||
}
|
||||
|
||||
// Returns a new Intent with the same action, data and categories as 'input'.
|
||||
private static Intent sanitize(Intent input) {
|
||||
Intent output = new Intent();
|
||||
output.setAction(input.getAction());
|
||||
output.setData(input.getData());
|
||||
|
||||
Set<String> categories = input.getCategories();
|
||||
if (categories != null) {
|
||||
for (String category : categories) {
|
||||
output.addCategory(category);
|
||||
}
|
||||
}
|
||||
// Don't bother copying extras and flags since AndroidComponentAddress (rightly) ignores them.
|
||||
// Don't bother copying package or ComponentName either, since we're about to set that.
|
||||
return output;
|
||||
}
|
||||
|
||||
final class PackageChangeReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// Get off the main thread and into the correct SynchronizationContext.
|
||||
syncContext.executeLater(IntentNameResolver.this::resolve);
|
||||
offloadExecutor.execute(syncContext::drain);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnprotectedReceiver") // All of these are protected system broadcasts.
|
||||
private void registerReceiver() {
|
||||
checkState(receiver == null, "Already registered!");
|
||||
receiver = new PackageChangeReceiver();
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addDataScheme("package");
|
||||
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||
|
||||
targetUserContext.registerReceiver(receiver, filter);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
// Clients running in direct boot mode must refresh() when the user is unlocked because
|
||||
// that's when `directBootAware=false` services become visible in queryIntentServices()
|
||||
// results. ACTION_BOOT_COMPLETED would work too but it's delivered with lower priority.
|
||||
targetUserContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUnregisterReceiver() {
|
||||
if (receiver != null) { // NameResolver API contract appears to allow shutdown without start().
|
||||
targetUserContext.unregisterReceiver(receiver);
|
||||
receiver = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static android.content.Intent.URI_INTENT_SCHEME;
|
||||
|
||||
import android.content.Intent;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.Args;
|
||||
import io.grpc.NameResolverProvider;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link NameResolverProvider} that handles Android-standard "intent:" target URIs, resolving
|
||||
* them to the list of {@link AndroidComponentAddress} that match by manifest intent filter.
|
||||
*/
|
||||
public final class IntentNameResolverProvider extends NameResolverProvider {
|
||||
|
||||
static final String ANDROID_INTENT_SCHEME = "intent";
|
||||
|
||||
@Override
|
||||
public String getDefaultScheme() {
|
||||
return ANDROID_INTENT_SCHEME;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public NameResolver newNameResolver(URI targetUri, final Args args) {
|
||||
if (Objects.equals(targetUri.getScheme(), ANDROID_INTENT_SCHEME)) {
|
||||
return new IntentNameResolver(parseUriArg(targetUri), args);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 3; // Lower than DNS so we don't accidentally become the default scheme for a registry.
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableSet<Class<? extends SocketAddress>> getProducedSocketAddressTypes() {
|
||||
return ImmutableSet.of(AndroidComponentAddress.class);
|
||||
}
|
||||
|
||||
private static Intent parseUriArg(URI targetUri) {
|
||||
try {
|
||||
return Intent.parseUri(targetUri.toString(), URI_INTENT_SCHEME);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ package io.grpc.binder.internal;
|
|||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.os.Parcel;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
|
@ -397,8 +396,7 @@ abstract class Outbound {
|
|||
@GuardedBy("this")
|
||||
void setDeadline(Deadline deadline) {
|
||||
headers.discardAll(TIMEOUT_KEY);
|
||||
long effectiveTimeoutNanos = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||
headers.put(TIMEOUT_KEY, effectiveTimeoutNanos);
|
||||
headers.put(TIMEOUT_KEY, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,14 +23,22 @@ import android.content.ComponentName;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.UserHandle;
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.binder.BinderChannelCredentials;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -85,6 +93,8 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
|||
private final Observer observer;
|
||||
private final Executor mainThreadExecutor;
|
||||
|
||||
private static volatile Method queryIntentServicesAsUserMethod;
|
||||
|
||||
@GuardedBy("this")
|
||||
private State state;
|
||||
|
||||
|
@ -183,18 +193,27 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
|||
bindResult = context.bindService(bindIntent, conn, flags);
|
||||
break;
|
||||
case BIND_SERVICE_AS_USER:
|
||||
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
|
||||
} else {
|
||||
return Status.INTERNAL.withDescription("Cross user Channel requires Android R+");
|
||||
}
|
||||
break;
|
||||
case DEVICE_POLICY_BIND_SEVICE_ADMIN:
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
bindResult =
|
||||
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
||||
channelCredentials.getDevicePolicyAdminComponentName(),
|
||||
bindIntent,
|
||||
conn,
|
||||
flags,
|
||||
targetUserHandle);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
bindResult =
|
||||
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
||||
channelCredentials.getDevicePolicyAdminComponentName(),
|
||||
bindIntent,
|
||||
conn,
|
||||
flags,
|
||||
targetUserHandle);
|
||||
} else {
|
||||
return Status.INTERNAL.withDescription(
|
||||
"Device policy admin binding requires Android R+");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!bindResult) {
|
||||
|
@ -247,6 +266,71 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
|||
}
|
||||
}
|
||||
|
||||
// Sadly the PackageManager#resolveServiceAsUser() API we need isn't part of the SDK or even a
|
||||
// @SystemApi as of this writing. Modern Android prevents even system apps from calling it, by any
|
||||
// means (https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces).
|
||||
// So instead we call queryIntentServicesAsUser(), which does more than we need but *is* a
|
||||
// @SystemApi in all the SDK versions where we support cross-user Channels.
|
||||
@Nullable
|
||||
private static ResolveInfo resolveServiceAsUser(
|
||||
PackageManager packageManager, Intent intent, int flags, UserHandle targetUserHandle) {
|
||||
List<ResolveInfo> results =
|
||||
queryIntentServicesAsUser(packageManager, intent, flags, targetUserHandle);
|
||||
// The first query result is "what would be returned by resolveService", per the javadoc.
|
||||
return (results != null && !results.isEmpty()) ? results.get(0) : null;
|
||||
}
|
||||
|
||||
// The cross-user Channel feature requires the client to be a system app so we assume @SystemApi
|
||||
// queryIntentServicesAsUser() is visible to us at runtime. It would be visible at build time too,
|
||||
// if our host system app were written to call it directly. We only have to use reflection here
|
||||
// because grpc-java is a library built outside the Android source tree where the compiler can't
|
||||
// see the "non-SDK" @SystemApis that we need.
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked") // Safe by PackageManager#queryIntentServicesAsUser spec in AOSP.
|
||||
private static List<ResolveInfo> queryIntentServicesAsUser(
|
||||
PackageManager packageManager, Intent intent, int flags, UserHandle targetUserHandle) {
|
||||
try {
|
||||
if (queryIntentServicesAsUserMethod == null) {
|
||||
synchronized (ServiceBinding.class) {
|
||||
if (queryIntentServicesAsUserMethod == null) {
|
||||
queryIntentServicesAsUserMethod =
|
||||
PackageManager.class.getMethod(
|
||||
"queryIntentServicesAsUser", Intent.class, int.class, UserHandle.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (List<ResolveInfo>)
|
||||
queryIntentServicesAsUserMethod.invoke(packageManager, intent, flags, targetUserHandle);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new VerifyException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@Override
|
||||
public ServiceInfo resolve() throws StatusException {
|
||||
checkState(sourceContext != null);
|
||||
PackageManager packageManager = sourceContext.getPackageManager();
|
||||
int flags = 0;
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
// Filter out non-'directBootAware' <service>s when 'targetUserHandle' is locked. Here's why:
|
||||
// Callers want 'bindIntent' to #resolve() to the same thing a follow-up call to #bind() will.
|
||||
// But bindService() *always* ignores services that can't presently be created for lack of
|
||||
// 'directBootAware'-ness. This flag explicitly tells resolveService() to act the same way.
|
||||
flags |= PackageManager.MATCH_DIRECT_BOOT_AUTO;
|
||||
}
|
||||
ResolveInfo resolveInfo =
|
||||
targetUserHandle != null
|
||||
? resolveServiceAsUser(packageManager, bindIntent, flags, targetUserHandle)
|
||||
: packageManager.resolveService(bindIntent, flags);
|
||||
if (resolveInfo == null) {
|
||||
throw Status.UNIMPLEMENTED // Same status code as when bindService() returns false.
|
||||
.withDescription("resolveService(" + bindIntent + " / " + targetUserHandle + ") was null")
|
||||
.asException();
|
||||
}
|
||||
return resolveInfo.serviceInfo;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void clearReferences() {
|
||||
sourceContext = null;
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A collection of static methods that wrap hidden Android "System APIs."
|
||||
*
|
||||
* <p>grpc-java can't call Android methods marked @SystemApi directly, even though many of our users
|
||||
* are "system apps" entitled to do so. Being a library built outside the Android source tree, these
|
||||
* "non-SDK" elements simply don't exist from our compiler's perspective. Instead we resort to
|
||||
* reflection but use the static wrappers found here to keep call sites readable and type safe.
|
||||
*
|
||||
* <p>Modern Android's JRE also limits the visibility of these methods at *runtime*. Only certain
|
||||
* privileged apps installed on the system image app can call them, even using reflection, and this
|
||||
* wrapper doesn't change that. Callers are responsible for ensuring that the host app actually has
|
||||
* the ability to call @SystemApis and all methods throw {@link ReflectiveOperationException} as a
|
||||
* reminder to do that. See
|
||||
* https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces for more.
|
||||
*/
|
||||
final class SystemApis {
|
||||
private static volatile Method createContextAsUserMethod;
|
||||
|
||||
// Not to be instantiated.
|
||||
private SystemApis() {}
|
||||
|
||||
/**
|
||||
* Returns a new Context object whose methods act as if they were running in the given user.
|
||||
*
|
||||
* @throws ReflectiveOperationException if SDK_INT < R or host app lacks @SystemApi visibility
|
||||
*/
|
||||
public static Context createContextAsUser(Context context, UserHandle userHandle, int flags)
|
||||
throws ReflectiveOperationException {
|
||||
if (createContextAsUserMethod == null) {
|
||||
synchronized (SystemApis.class) {
|
||||
if (createContextAsUserMethod == null) {
|
||||
createContextAsUserMethod =
|
||||
Context.class.getMethod("createContextAsUser", UserHandle.class, int.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (Context) createContextAsUserMethod.invoke(context, userHandle, flags);
|
||||
}
|
||||
}
|
|
@ -22,7 +22,13 @@ 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 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;
|
||||
|
@ -46,11 +52,13 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.annotation.LooperMode.Mode;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
||||
public final class RobolectricBinderSecurityTest {
|
||||
|
||||
|
@ -62,10 +70,33 @@ public final class RobolectricBinderSecurityTest {
|
|||
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);
|
||||
|
||||
ServiceInfo serviceInfo = new ServiceInfo();
|
||||
serviceInfo.name = "SomeService";
|
||||
serviceInfo.packageName = serverAppInfo.packageName;
|
||||
serviceInfo.applicationInfo = serverAppInfo;
|
||||
shadowOf(context.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||
|
||||
AndroidComponentAddress listenAddress =
|
||||
AndroidComponentAddress.forRemoteComponent(context.getPackageName(), "HostService");
|
||||
AndroidComponentAddress.forRemoteComponent(serviceInfo.packageName, serviceInfo.name);
|
||||
|
||||
MethodDescriptor<Empty, Empty> methodDesc = getMethodDescriptor();
|
||||
ServerCallHandler<Empty, Empty> callHandler =
|
||||
|
@ -110,6 +141,7 @@ public final class RobolectricBinderSecurityTest {
|
|||
checkNotNull(binderReceiver.get()));
|
||||
channel =
|
||||
BinderChannelBuilder.forAddress(listenAddress, context)
|
||||
.preAuthorizeServers(preAuthServersParam)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -56,12 +56,12 @@ public final class BinderServerTransportTest {
|
|||
|
||||
@Mock IBinder mockBinder;
|
||||
|
||||
BinderTransport.BinderServerTransport transport;
|
||||
BinderServerTransport transport;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
transport =
|
||||
new BinderTransport.BinderServerTransport(
|
||||
new BinderServerTransport(
|
||||
new FixedObjectPool<>(executorService),
|
||||
Attributes.EMPTY,
|
||||
ImmutableList.of(),
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Application;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.ResolutionResult;
|
||||
import io.grpc.NameResolver.ServiceConfigParser;
|
||||
import io.grpc.NameResolverProvider;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.binder.ApiConstants;
|
||||
import java.net.URI;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoTestRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** A test for IntentNameResolverProvider. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class IntentNameResolverProviderTest {
|
||||
|
||||
private final Application appContext = ApplicationProvider.getApplicationContext();
|
||||
private final SynchronizationContext syncContext = newSynchronizationContext();
|
||||
private final NameResolver.Args args = newNameResolverArgs();
|
||||
|
||||
private NameResolverProvider provider;
|
||||
|
||||
@Rule public MockitoTestRule mockitoTestRule = MockitoJUnit.testRule(this);
|
||||
@Mock public NameResolver.Listener2 mockListener;
|
||||
@Captor public ArgumentCaptor<ResolutionResult> resultCaptor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
provider = new IntentNameResolverProvider();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProviderScheme_returnsIntentScheme() throws Exception {
|
||||
assertThat(provider.getDefaultScheme())
|
||||
.isEqualTo(IntentNameResolverProvider.ANDROID_INTENT_SCHEME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoResolverForUnknownScheme_returnsNull() throws Exception {
|
||||
assertThat(provider.newNameResolver(new URI("random://uri"), args)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionWithBadUri_throwsIllegalArg() throws Exception {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> provider.newNameResolver(new URI("intent:xxx#Intent;e.x=1;end;"), args));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolverForIntentScheme_returnsResolver() throws Exception {
|
||||
URI uri = new URI("intent://authority/path#Intent;action=action;scheme=scheme;end");
|
||||
NameResolver resolver = provider.newNameResolver(uri, args);
|
||||
assertThat(resolver).isNotNull();
|
||||
assertThat(resolver.getServiceAuthority()).isEqualTo("localhost");
|
||||
syncContext.execute(() -> resolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(resultCaptor.getValue().getAddressesOrError()).isNotNull();
|
||||
syncContext.execute(resolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
/** Returns a new test-specific {@link NameResolver.Args} instance. */
|
||||
private NameResolver.Args newNameResolverArgs() {
|
||||
return NameResolver.Args.newBuilder()
|
||||
.setDefaultPort(-1)
|
||||
.setProxyDetector((target) -> null) // No proxies here.
|
||||
.setSynchronizationContext(syncContext)
|
||||
.setOffloadExecutor(ContextCompat.getMainExecutor(appContext))
|
||||
.setServiceConfigParser(mock(ServiceConfigParser.class))
|
||||
.setArg(ApiConstants.SOURCE_ANDROID_CONTEXT, appContext)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static SynchronizationContext newSynchronizationContext() {
|
||||
return new SynchronizationContext(
|
||||
(thread, exception) -> {
|
||||
throw new AssertionError(exception);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_REPLACED;
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static android.os.Process.myUserHandle;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.grpc.EquivalentAddressGroup;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.ResolutionResult;
|
||||
import io.grpc.NameResolver.ServiceConfigParser;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusOr;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import io.grpc.binder.ApiConstants;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoTestRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
|
||||
/** A test for IntentNameResolverProvider. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class IntentNameResolverTest {
|
||||
|
||||
private static final ComponentName SOME_COMPONENT_NAME =
|
||||
new ComponentName("com.foo.bar", "SomeComponent");
|
||||
private static final ComponentName ANOTHER_COMPONENT_NAME =
|
||||
new ComponentName("org.blah", "AnotherComponent");
|
||||
private final Application appContext = ApplicationProvider.getApplicationContext();
|
||||
private final SynchronizationContext syncContext = newSynchronizationContext();
|
||||
private final NameResolver.Args args = newNameResolverArgs().build();
|
||||
|
||||
private final ShadowPackageManager shadowPackageManager =
|
||||
shadowOf(appContext.getPackageManager());
|
||||
|
||||
@Rule public MockitoTestRule mockitoTestRule = MockitoJUnit.testRule(this);
|
||||
@Mock public NameResolver.Listener2 mockListener;
|
||||
@Captor public ArgumentCaptor<ResolutionResult> resultCaptor;
|
||||
|
||||
@Test
|
||||
public void testResolverForIntentScheme_returnsResolverWithLocalHostAuthority() throws Exception {
|
||||
NameResolver resolver = newNameResolver(newIntent());
|
||||
assertThat(resolver).isNotNull();
|
||||
assertThat(resolver.getServiceAuthority()).isEqualTo("localhost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionWithoutServicesAvailable_returnsUnimplemented() throws Exception {
|
||||
NameResolver nameResolver = newNameResolver(newIntent());
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(resultCaptor.getValue().getAddressesOrError().getStatus().getCode())
|
||||
.isEqualTo(Status.UNIMPLEMENTED.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionWithMultipleServicesAvailable_returnsAndroidComponentAddresses()
|
||||
throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
// Adds another valid Service
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitResolutionByComponent_returnsRestrictedResults() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver =
|
||||
newNameResolver(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME));
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitResolutionByPackage_returnsRestrictedResults() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver =
|
||||
newNameResolver(intent.cloneFilter().setPackage(ANOTHER_COMPONENT_NAME.getPackageName()));
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolution_setsPreAuthEagAttribute() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||
assertThat(
|
||||
getEagsOrThrow(resultCaptor.getValue()).stream()
|
||||
.map(EquivalentAddressGroup::getAttributes)
|
||||
.collect(toImmutableList())
|
||||
.get(0)
|
||||
.get(ApiConstants.PRE_AUTH_SERVER_OVERRIDE))
|
||||
.isTrue();
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceRemoved_pushesUpdatedAndroidComponentAddresses() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
shadowPackageManager.removeService(ANOTHER_COMPONENT_NAME);
|
||||
broadcastPackageChange(ACTION_PACKAGE_REPLACED, ANOTHER_COMPONENT_NAME.getPackageName());
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verifyNoMoreInteractions(mockListener);
|
||||
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 30)
|
||||
public void testTargetAndroidUser_pushesUpdatedAddresses() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
NameResolver nameResolver =
|
||||
newNameResolver(
|
||||
intent,
|
||||
newNameResolverArgs().setArg(ApiConstants.TARGET_ANDROID_USER, myUserHandle()).build());
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(resultCaptor.getValue().getAddressesOrError().getStatus().getCode())
|
||||
.isEqualTo(Status.UNIMPLEMENTED.getCode());
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
broadcastPackageChange(ACTION_PACKAGE_ADDED, SOME_COMPONENT_NAME.getPackageName());
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
ImmutableList.of(
|
||||
AndroidComponentAddress.newBuilder()
|
||||
.setTargetUser(myUserHandle())
|
||||
.setBindIntent(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME))
|
||||
.build()));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verifyNoMoreInteractions(mockListener);
|
||||
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 29)
|
||||
public void testTargetAndroidUser_notSupported_throwsWithHelpfulMessage() throws Exception {
|
||||
NameResolver.Args args =
|
||||
newNameResolverArgs().setArg(ApiConstants.TARGET_ANDROID_USER, myUserHandle()).build();
|
||||
IllegalArgumentException iae =
|
||||
assertThrows(IllegalArgumentException.class, () -> newNameResolver(newIntent(), args));
|
||||
assertThat(iae.getMessage()).contains("TARGET_ANDROID_USER");
|
||||
assertThat(iae.getMessage()).contains("SDK_INT >= R");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 29)
|
||||
public void testServiceAppearsUponBootComplete_pushesUpdatedAndroidComponentAddresses()
|
||||
throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
// Suppose this directBootAware=true Service appears in PackageManager before a user unlock.
|
||||
shadowOf(appContext.getSystemService(UserManager.class)).setUserUnlocked(false);
|
||||
ServiceInfo someServiceInfo = shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
someServiceInfo.directBootAware = true;
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||
|
||||
// TODO(b/331618070): Robolectric doesn't yet support ServiceInfo.directBootAware filtering.
|
||||
// Simulate support by waiting for a user unlock to add this !directBootAware Service.
|
||||
ServiceInfo anotherServiceInfo =
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
anotherServiceInfo.directBootAware = false;
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
shadowOf(appContext.getSystemService(UserManager.class)).setUserUnlocked(true);
|
||||
broadcastUserUnlocked(myUserHandle());
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verifyNoMoreInteractions(mockListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh_returnsSameAndroidComponentAddresses() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::refresh);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh_collapsesMultipleRequestsIntoOneLookup() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener)); // Should kick off the 1st lookup.
|
||||
syncContext.execute(nameResolver::refresh); // Should queue a lookup to run when 1st finishes.
|
||||
syncContext.execute(nameResolver::refresh); // Should be ignored since a lookup is already Q'd.
|
||||
syncContext.execute(nameResolver::refresh); // Also ignored.
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
private void broadcastPackageChange(String action, String pkgName) {
|
||||
Intent broadcast = new Intent();
|
||||
broadcast.setAction(action);
|
||||
broadcast.setData(Uri.parse("package:" + pkgName));
|
||||
appContext.sendBroadcast(broadcast);
|
||||
}
|
||||
|
||||
private void broadcastUserUnlocked(UserHandle userHandle) {
|
||||
Intent unlockedBroadcast = new Intent(Intent.ACTION_USER_UNLOCKED);
|
||||
unlockedBroadcast.putExtra(Intent.EXTRA_USER, userHandle);
|
||||
appContext.sendBroadcast(unlockedBroadcast);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionOnResultThrows_onErrorNotCalled() throws Exception {
|
||||
RetainingUncaughtExceptionHandler exceptionHandler = new RetainingUncaughtExceptionHandler();
|
||||
SynchronizationContext syncContext = new SynchronizationContext(exceptionHandler);
|
||||
Intent intent = newIntent();
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, newFilterMatching(intent));
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
class SomeRuntimeException extends RuntimeException {}
|
||||
doThrow(SomeRuntimeException.class).when(mockListener).onResult2(any());
|
||||
|
||||
NameResolver nameResolver =
|
||||
newNameResolver(
|
||||
intent, newNameResolverArgs().setSynchronizationContext(syncContext).build());
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener).onResult2(any());
|
||||
verify(mockListener, never()).onError(any());
|
||||
assertThat(exceptionHandler.uncaught).hasSize(1);
|
||||
assertThat(exceptionHandler.uncaught.get(0)).isInstanceOf(SomeRuntimeException.class);
|
||||
}
|
||||
|
||||
private static Intent newIntent() {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction("test.action");
|
||||
intent.setData(Uri.parse("grpc:ServiceName"));
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static IntentFilter newFilterMatching(Intent intent) {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
if (intent.getAction() != null) {
|
||||
filter.addAction(intent.getAction());
|
||||
}
|
||||
Uri data = intent.getData();
|
||||
if (data != null) {
|
||||
if (data.getScheme() != null) {
|
||||
filter.addDataScheme(data.getScheme());
|
||||
}
|
||||
if (data.getSchemeSpecificPart() != null) {
|
||||
filter.addDataSchemeSpecificPart(data.getSchemeSpecificPart(), 0);
|
||||
}
|
||||
}
|
||||
Set<String> categories = intent.getCategories();
|
||||
if (categories != null) {
|
||||
for (String category : categories) {
|
||||
filter.addCategory(category);
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static List<EquivalentAddressGroup> getEagsOrThrow(ResolutionResult result) {
|
||||
StatusOr<List<EquivalentAddressGroup>> eags = result.getAddressesOrError();
|
||||
if (!eags.hasValue()) {
|
||||
throw eags.getStatus().asRuntimeException();
|
||||
}
|
||||
return eags.getValue();
|
||||
}
|
||||
|
||||
// Extracts just the addresses from 'result's EquivalentAddressGroups.
|
||||
private static ImmutableList<List<SocketAddress>> getAddressesOrThrow(ResolutionResult result) {
|
||||
return getEagsOrThrow(result).stream()
|
||||
.map(EquivalentAddressGroup::getAddresses)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
// Converts given Intents to a list of ACAs, for convenient comparison with getAddressesOrThrow().
|
||||
private static ImmutableList<AndroidComponentAddress> toAddressList(Intent... bindIntents) {
|
||||
ImmutableList.Builder<AndroidComponentAddress> builder = ImmutableList.builder();
|
||||
for (Intent bindIntent : bindIntents) {
|
||||
builder.add(AndroidComponentAddress.forBindIntent(bindIntent));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private NameResolver newNameResolver(Intent targetIntent) {
|
||||
return newNameResolver(targetIntent, args);
|
||||
}
|
||||
|
||||
private NameResolver newNameResolver(Intent targetIntent, NameResolver.Args args) {
|
||||
return new IntentNameResolver(targetIntent, args);
|
||||
}
|
||||
|
||||
/** Returns a new test-specific {@link NameResolver.Args} instance. */
|
||||
private NameResolver.Args.Builder newNameResolverArgs() {
|
||||
return NameResolver.Args.newBuilder()
|
||||
.setDefaultPort(-1)
|
||||
.setProxyDetector((target) -> null) // No proxies here.
|
||||
.setSynchronizationContext(syncContext)
|
||||
.setOffloadExecutor(ContextCompat.getMainExecutor(appContext))
|
||||
.setArg(ApiConstants.SOURCE_ANDROID_CONTEXT, appContext)
|
||||
.setServiceConfigParser(mock(ServiceConfigParser.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a test {@link SynchronizationContext}.
|
||||
*
|
||||
* <p>Exceptions will cause the test to fail with {@link AssertionError}.
|
||||
*/
|
||||
private static SynchronizationContext newSynchronizationContext() {
|
||||
return new SynchronizationContext(
|
||||
(thread, exception) -> {
|
||||
throw new AssertionError(exception);
|
||||
});
|
||||
}
|
||||
|
||||
static final class RetainingUncaughtExceptionHandler implements UncaughtExceptionHandler {
|
||||
final ArrayList<Throwable> uncaught = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
|
||||
uncaught.add(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,13 +16,29 @@
|
|||
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
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;
|
||||
|
@ -33,12 +49,20 @@ 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.robolectric.RobolectricTestRunner;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.annotation.LooperMode.Mode;
|
||||
import org.robolectric.shadows.ShadowBinder;
|
||||
|
||||
/**
|
||||
* All of the AbstractTransportTest cases applied to {@link BinderTransport} running in a
|
||||
|
@ -52,7 +76,7 @@ import org.robolectric.annotation.LooperMode.Mode;
|
|||
* meaning test cases don't run on the main thread. This supports the AbstractTransportTest approach
|
||||
* where the test thread frequently blocks waiting for transport state changes to take effect.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
||||
public final class RobolectricBinderTransportTest extends AbstractTransportTest {
|
||||
|
||||
|
@ -64,14 +88,57 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest
|
|||
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(application.getPackageName(), "HostService")
|
||||
.setAction("io.grpc.action.BIND." + nextServerAddress++));
|
||||
AndroidComponentAddress listenAddr =
|
||||
AndroidComponentAddress.forBindIntent(
|
||||
new Intent()
|
||||
.setClassName(serviceInfo.packageName, serviceInfo.name)
|
||||
.setAction("io.grpc.action.BIND." + nextServerAddress++));
|
||||
|
||||
BinderServer binderServer =
|
||||
new BinderServer.Builder()
|
||||
|
@ -81,6 +148,7 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest
|
|||
.setStreamTracerFactories(streamTracerFactories)
|
||||
.build();
|
||||
|
||||
shadowOf(application.getPackageManager()).addServiceIfNotPresent(listenAddr.getComponent());
|
||||
shadowOf(application)
|
||||
.setComponentNameAndServiceForBindServiceForIntent(
|
||||
listenAddr.asBindIntent(), listenAddr.getComponent(), binderServer.getHostBinder());
|
||||
|
@ -97,22 +165,30 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest
|
|||
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) {
|
||||
BinderClientTransportFactory.Builder builder =
|
||||
new BinderClientTransportFactory.Builder()
|
||||
.setSourceContext(application)
|
||||
.setScheduledExecutorPool(executorServicePool)
|
||||
.setOffloadExecutorPool(offloadExecutorPool);
|
||||
|
||||
ClientTransportOptions options = new ClientTransportOptions();
|
||||
options.setEagAttributes(eagAttrs());
|
||||
options.setChannelLogger(transportLogger());
|
||||
|
||||
return new BinderTransport.BinderClientTransport(
|
||||
builder.buildClientTransportFactory(),
|
||||
(AndroidComponentAddress) server.getListenSocketAddress(),
|
||||
options);
|
||||
return newClientTransportBuilder()
|
||||
.setServerAddress(server.getListenSocketAddress())
|
||||
.setOptions(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,6 +196,74 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest
|
|||
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
|
||||
|
|
|
@ -19,6 +19,7 @@ package io.grpc.binder.internal;
|
|||
import static android.content.Context.BIND_AUTO_CREATE;
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
|
@ -27,6 +28,8 @@ import android.app.admin.DevicePolicyManager;
|
|||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.UserHandle;
|
||||
|
@ -34,6 +37,7 @@ import androidx.core.content.ContextCompat;
|
|||
import androidx.test.core.app.ApplicationProvider;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.Status.Code;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.binder.BinderChannelCredentials;
|
||||
import io.grpc.binder.internal.Bindable.Observer;
|
||||
import java.util.Arrays;
|
||||
|
@ -59,6 +63,7 @@ public final class ServiceBindingTest {
|
|||
|
||||
private Application appContext;
|
||||
private ComponentName serviceComponent;
|
||||
private ServiceInfo serviceInfo = new ServiceInfo();
|
||||
private ShadowApplication shadowApplication;
|
||||
private TestObserver observer;
|
||||
private ServiceBinding binding;
|
||||
|
@ -67,13 +72,17 @@ public final class ServiceBindingTest {
|
|||
public void setUp() {
|
||||
appContext = ApplicationProvider.getApplicationContext();
|
||||
serviceComponent = new ComponentName("DUMMY", "SERVICE");
|
||||
serviceInfo.packageName = serviceComponent.getPackageName();
|
||||
serviceInfo.name = serviceComponent.getClassName();
|
||||
observer = new TestObserver();
|
||||
|
||||
shadowApplication = shadowOf(appContext);
|
||||
shadowApplication.setComponentNameAndServiceForBindService(serviceComponent, mockBinder);
|
||||
shadowOf(appContext.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||
|
||||
// Don't call onServiceDisconnected() upon unbindService(), just like the real Android doesn't.
|
||||
shadowApplication.setUnbindServiceCallsOnServiceDisconnected(false);
|
||||
shadowApplication.setBindServiceCallsOnServiceConnectedDirectly(false);
|
||||
|
||||
binding = newBuilder().build();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
@ -276,6 +285,93 @@ public final class ServiceBindingTest {
|
|||
assertThat(binding.isSourceContextCleared()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolve() throws Exception {
|
||||
serviceInfo.processName = "x"; // ServiceInfo has no equals() so look for one distinctive field.
|
||||
shadowOf(appContext.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||
ServiceInfo resolvedServiceInfo = binding.resolve();
|
||||
assertThat(resolvedServiceInfo.processName).isEqualTo(serviceInfo.processName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 33)
|
||||
public void testResolveWithTargetUserHandle() throws Exception {
|
||||
serviceInfo.processName = "x"; // ServiceInfo has no equals() so look for one distinctive field.
|
||||
// Robolectric just ignores the user arg to resolveServiceAsUser() so this is all we can do.
|
||||
shadowOf(appContext.getPackageManager()).addOrUpdateService(serviceInfo);
|
||||
binding = newBuilder().setTargetUserHandle(generateUserHandle(/* userId= */ 0)).build();
|
||||
ServiceInfo resolvedServiceInfo = binding.resolve();
|
||||
assertThat(resolvedServiceInfo.processName).isEqualTo(serviceInfo.processName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveNonExistentServiceThrows() throws Exception {
|
||||
ComponentName doesNotExistService = new ComponentName("does.not.exist", "NoService");
|
||||
binding = newBuilder().setTargetComponent(doesNotExistService).build();
|
||||
StatusException statusException = assertThrows(StatusException.class, binding::resolve);
|
||||
assertThat(statusException.getStatus().getCode()).isEqualTo(Code.UNIMPLEMENTED);
|
||||
assertThat(statusException.getStatus().getDescription()).contains("does.not.exist");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 33)
|
||||
public void testResolveNonExistentServiceWithTargetUserThrows() throws Exception {
|
||||
ComponentName doesNotExistService = new ComponentName("does.not.exist", "NoService");
|
||||
binding =
|
||||
newBuilder()
|
||||
.setTargetUserHandle(generateUserHandle(/* userId= */ 12345))
|
||||
.setTargetComponent(doesNotExistService)
|
||||
.build();
|
||||
StatusException statusException = assertThrows(StatusException.class, binding::resolve);
|
||||
assertThat(statusException.getStatus().getCode()).isEqualTo(Code.UNIMPLEMENTED);
|
||||
assertThat(statusException.getStatus().getDescription()).contains("does.not.exist");
|
||||
assertThat(statusException.getStatus().getDescription()).contains("12345");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 30)
|
||||
public void testBindService_doesNotThrowInternalErrorWhenSdkAtLeastR() {
|
||||
UserHandle userHandle = generateUserHandle(/* userId= */ 12345);
|
||||
binding = newBuilder().setTargetUserHandle(userHandle).build();
|
||||
binding.bind();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertThat(Build.VERSION.SDK_INT).isEqualTo(Build.VERSION_CODES.R);
|
||||
assertThat(observer.unboundReason).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 28)
|
||||
public void testBindServiceAsUser_returnsErrorWhenSdkBelowR() {
|
||||
UserHandle userHandle = generateUserHandle(/* userId= */ 12345);
|
||||
binding = newBuilder().setTargetUserHandle(userHandle).build();
|
||||
binding.bind();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.INTERNAL);
|
||||
assertThat(observer.unboundReason.getDescription())
|
||||
.isEqualTo("Cross user Channel requires Android R+");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 28)
|
||||
public void testDevicePolicyBlind_returnsErrorWhenSdkBelowR() {
|
||||
String deviceAdminClassName = "DevicePolicyAdmin";
|
||||
ComponentName adminComponent = new ComponentName(appContext, deviceAdminClassName);
|
||||
allowBindDeviceAdminForUser(appContext, adminComponent, 10);
|
||||
binding =
|
||||
newBuilder()
|
||||
.setTargetUserHandle(UserHandle.getUserHandleForUid(10))
|
||||
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
|
||||
.build();
|
||||
binding.bind();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.INTERNAL);
|
||||
assertThat(observer.unboundReason.getDescription())
|
||||
.isEqualTo("Device policy admin binding requires Android R+");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 30)
|
||||
public void testBindWithDeviceAdmin() throws Exception {
|
||||
|
@ -284,7 +380,7 @@ public final class ServiceBindingTest {
|
|||
allowBindDeviceAdminForUser(appContext, adminComponent, /* userId= */ 0);
|
||||
binding =
|
||||
newBuilder()
|
||||
.setTargetUserHandle(UserHandle.getUserHandleForUid(/* userId= */ 0))
|
||||
.setTargetUserHandle(UserHandle.getUserHandleForUid(/* uid= */ 0))
|
||||
.setTargetUserHandle(generateUserHandle(/* userId= */ 0))
|
||||
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
|
||||
.build();
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import io.grpc.ChannelLogger;
|
||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||
import io.grpc.internal.TestUtils.NoopChannelLogger;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* Helps unit tests create {@link BinderClientTransport} instances without having to mention
|
||||
* irrelevant details (go/tott/719).
|
||||
*/
|
||||
public class BinderClientTransportBuilder {
|
||||
private BinderClientTransportFactory factory;
|
||||
private SocketAddress serverAddress;
|
||||
private ChannelLogger channelLogger = new NoopChannelLogger();
|
||||
private io.grpc.internal.ClientTransportFactory.ClientTransportOptions options =
|
||||
new ClientTransportOptions();
|
||||
|
||||
public BinderClientTransportBuilder setServerAddress(SocketAddress serverAddress) {
|
||||
this.serverAddress = checkNotNull(serverAddress);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinderClientTransportBuilder setChannelLogger(ChannelLogger channelLogger) {
|
||||
this.channelLogger = checkNotNull(channelLogger);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinderClientTransportBuilder setOptions(ClientTransportOptions options) {
|
||||
this.options = checkNotNull(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinderClientTransportBuilder setFactory(BinderClientTransportFactory factory) {
|
||||
this.factory = checkNotNull(factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BinderClientTransport build() {
|
||||
return factory.newClientTransport(
|
||||
checkNotNull(serverAddress), checkNotNull(options), checkNotNull(channelLogger));
|
||||
}
|
||||
}
|
12
build.gradle
12
build.gradle
|
@ -21,11 +21,11 @@ subprojects {
|
|||
apply plugin: "net.ltgt.errorprone"
|
||||
|
||||
group = "io.grpc"
|
||||
version = "1.74.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
||||
version = "1.76.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
||||
|
||||
repositories {
|
||||
maven { // The google mirror is less flaky than mavenCentral()
|
||||
url "https://maven-central.storage-download.googleapis.com/maven2/"
|
||||
url = "https://maven-central.storage-download.googleapis.com/maven2/"
|
||||
metadataSources {
|
||||
mavenPom()
|
||||
ignoreGradleMetadataRedirection()
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -410,7 +410,7 @@ subprojects {
|
|||
}
|
||||
|
||||
signing {
|
||||
required false
|
||||
required = false
|
||||
sign publishing.publications.maven
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ dependencies {
|
|||
}
|
||||
|
||||
tasks.named("javadoc").configure {
|
||||
failOnError false // no public or protected classes found to document
|
||||
failOnError = false // no public or protected classes found to document
|
||||
exclude 'io/grpc/census/internal/**'
|
||||
exclude 'io/grpc/census/Internal*'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.census;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.base.Supplier;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.ExperimentalApi;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.ServerBuilder;
|
||||
import io.grpc.ServerStreamTracer;
|
||||
import io.opencensus.trace.Tracing;
|
||||
|
||||
/**
|
||||
* The entrypoint for OpenCensus instrumentation functionality in gRPC.
|
||||
*
|
||||
* <p>GrpcCensus uses {@link io.opencensus.api.OpenCensus} APIs for instrumentation.
|
||||
*
|
||||
*/
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12178")
|
||||
public final class GrpcCensus {
|
||||
|
||||
private final boolean statsEnabled;
|
||||
private final boolean tracingEnabled;
|
||||
|
||||
private GrpcCensus(Builder builder) {
|
||||
this.statsEnabled = builder.statsEnabled;
|
||||
this.tracingEnabled = builder.tracingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new builder for {@link GrpcCensus}.
|
||||
*/
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
private static final Supplier<Stopwatch> STOPWATCH_SUPPLIER = new Supplier<Stopwatch>() {
|
||||
@Override
|
||||
public Stopwatch get() {
|
||||
return Stopwatch.createUnstarted();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Configures a {@link ServerBuilder} to enable census stats and tracing.
|
||||
*
|
||||
* @param serverBuilder The server builder to configure.
|
||||
* @return The configured server builder.
|
||||
*/
|
||||
public <T extends ServerBuilder<T>> T configureServerBuilder(T serverBuilder) {
|
||||
if (statsEnabled) {
|
||||
serverBuilder.addStreamTracerFactory(newServerStatsStreamTracerFactory());
|
||||
}
|
||||
if (tracingEnabled) {
|
||||
serverBuilder.addStreamTracerFactory(newServerTracingStreamTracerFactory());
|
||||
}
|
||||
return serverBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a {@link ManagedChannelBuilder} to enable census stats and tracing.
|
||||
*
|
||||
* @param channelBuilder The channel builder to configure.
|
||||
* @return The configured channel builder.
|
||||
*/
|
||||
public <T extends ManagedChannelBuilder<T>> T configureChannelBuilder(T channelBuilder) {
|
||||
if (statsEnabled) {
|
||||
channelBuilder.intercept(newClientStatsInterceptor());
|
||||
}
|
||||
if (tracingEnabled) {
|
||||
channelBuilder.intercept(newClientTracingInterceptor());
|
||||
}
|
||||
return channelBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ClientInterceptor} with default stats implementation.
|
||||
*/
|
||||
private static ClientInterceptor newClientStatsInterceptor() {
|
||||
CensusStatsModule censusStats =
|
||||
new CensusStatsModule(
|
||||
STOPWATCH_SUPPLIER,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true);
|
||||
return censusStats.getClientInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ClientInterceptor} with default tracing implementation.
|
||||
*/
|
||||
private static ClientInterceptor newClientTracingInterceptor() {
|
||||
CensusTracingModule censusTracing =
|
||||
new CensusTracingModule(
|
||||
Tracing.getTracer(),
|
||||
Tracing.getPropagationComponent().getBinaryFormat());
|
||||
return censusTracing.getClientInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ServerStreamTracer.Factory} with default stats implementation.
|
||||
*/
|
||||
private static ServerStreamTracer.Factory newServerStatsStreamTracerFactory() {
|
||||
CensusStatsModule censusStats =
|
||||
new CensusStatsModule(
|
||||
STOPWATCH_SUPPLIER,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true);
|
||||
return censusStats.getServerTracerFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ServerStreamTracer.Factory} with default tracing implementation.
|
||||
*/
|
||||
private static ServerStreamTracer.Factory newServerTracingStreamTracerFactory() {
|
||||
CensusTracingModule censusTracing =
|
||||
new CensusTracingModule(
|
||||
Tracing.getTracer(),
|
||||
Tracing.getPropagationComponent().getBinaryFormat());
|
||||
return censusTracing.getServerTracerFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link GrpcCensus}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private boolean statsEnabled = true;
|
||||
private boolean tracingEnabled = true;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables stats collection.
|
||||
*/
|
||||
public Builder disableStats() {
|
||||
this.statsEnabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables tracing.
|
||||
*/
|
||||
public Builder disableTracing() {
|
||||
this.tracingEnabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link GrpcCensus}.
|
||||
*/
|
||||
public GrpcCensus build() {
|
||||
return new GrpcCensus(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ cc_binary(
|
|||
|
||||
java_library(
|
||||
name = "java_grpc_library_deps__do_not_reference",
|
||||
visibility = ["//xds:__pkg__"],
|
||||
exports = [
|
||||
"//api",
|
||||
"//protobuf",
|
||||
|
|
|
@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler (version 1.74.0-SNAPSHOT)",
|
||||
value = "by gRPC proto compiler (version 1.76.0-SNAPSHOT)",
|
||||
comments = "Source: grpc/testing/compiler/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
@java.lang.Deprecated
|
||||
|
|
|
@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler (version 1.74.0-SNAPSHOT)",
|
||||
value = "by gRPC proto compiler (version 1.76.0-SNAPSHOT)",
|
||||
comments = "Source: grpc/testing/compiler/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class TestServiceGrpc {
|
||||
|
|
|
@ -21,7 +21,6 @@ import static com.google.common.base.Preconditions.checkState;
|
|||
import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY;
|
||||
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
|
||||
import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
@ -124,8 +123,7 @@ public abstract class AbstractClientStream extends AbstractStream
|
|||
@Override
|
||||
public void setDeadline(Deadline deadline) {
|
||||
headers.discardAll(TIMEOUT_KEY);
|
||||
long effectiveTimeout = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||
headers.put(TIMEOUT_KEY, effectiveTimeout);
|
||||
headers.put(TIMEOUT_KEY, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -96,15 +96,13 @@ public class DelayedClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
|
|||
private ScheduledFuture<?> scheduleDeadlineIfNeeded(
|
||||
ScheduledExecutorService scheduler, @Nullable Deadline deadline) {
|
||||
Deadline contextDeadline = context.getDeadline();
|
||||
if (deadline == null && contextDeadline == null) {
|
||||
return null;
|
||||
}
|
||||
long remainingNanos = Long.MAX_VALUE;
|
||||
if (deadline != null) {
|
||||
String deadlineName;
|
||||
long remainingNanos;
|
||||
if (deadline != null && isAbeforeB(deadline, contextDeadline)) {
|
||||
deadlineName = "CallOptions";
|
||||
remainingNanos = deadline.timeRemaining(NANOSECONDS);
|
||||
}
|
||||
|
||||
if (contextDeadline != null && contextDeadline.timeRemaining(NANOSECONDS) < remainingNanos) {
|
||||
} else if (contextDeadline != null) {
|
||||
deadlineName = "Context";
|
||||
remainingNanos = contextDeadline.timeRemaining(NANOSECONDS);
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
StringBuilder builder =
|
||||
|
@ -121,29 +119,29 @@ public class DelayedClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
|
|||
}
|
||||
logger.fine(builder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
long seconds = Math.abs(remainingNanos) / TimeUnit.SECONDS.toNanos(1);
|
||||
long nanos = Math.abs(remainingNanos) % TimeUnit.SECONDS.toNanos(1);
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
String deadlineName = isAbeforeB(contextDeadline, deadline) ? "Context" : "CallOptions";
|
||||
if (remainingNanos < 0) {
|
||||
buf.append("ClientCall started after ");
|
||||
buf.append(deadlineName);
|
||||
buf.append(" deadline was exceeded. Deadline has been exceeded for ");
|
||||
} else {
|
||||
buf.append("Deadline ");
|
||||
buf.append(deadlineName);
|
||||
buf.append(" will be exceeded in ");
|
||||
return null;
|
||||
}
|
||||
buf.append(seconds);
|
||||
buf.append(String.format(Locale.US, ".%09d", nanos));
|
||||
buf.append("s. ");
|
||||
|
||||
/* Cancels the call if deadline exceeded prior to the real call being set. */
|
||||
class DeadlineExceededRunnable implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
long seconds = Math.abs(remainingNanos) / TimeUnit.SECONDS.toNanos(1);
|
||||
long nanos = Math.abs(remainingNanos) % TimeUnit.SECONDS.toNanos(1);
|
||||
StringBuilder buf = new StringBuilder();
|
||||
if (remainingNanos < 0) {
|
||||
buf.append("ClientCall started after ");
|
||||
buf.append(deadlineName);
|
||||
buf.append(" deadline was exceeded. Deadline has been exceeded for ");
|
||||
} else {
|
||||
buf.append("Deadline ");
|
||||
buf.append(deadlineName);
|
||||
buf.append(" was exceeded after ");
|
||||
}
|
||||
buf.append(seconds);
|
||||
buf.append(String.format(Locale.US, ".%09d", nanos));
|
||||
buf.append("s");
|
||||
cancel(
|
||||
Status.DEADLINE_EXCEEDED.withDescription(buf.toString()),
|
||||
// We should not cancel the call if the realCall is set because there could be a
|
||||
|
|
|
@ -219,7 +219,7 @@ public final class GrpcUtil {
|
|||
|
||||
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
|
||||
|
||||
public static final String IMPLEMENTATION_VERSION = "1.74.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
|
||||
public static final String IMPLEMENTATION_VERSION = "1.76.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
|
||||
|
||||
/**
|
||||
* The default timeout in nanos for a keepalive ping request.
|
||||
|
@ -651,12 +651,14 @@ public final class GrpcUtil {
|
|||
static class TimeoutMarshaller implements Metadata.AsciiMarshaller<Long> {
|
||||
|
||||
@Override
|
||||
public String toAsciiString(Long timeoutNanos) {
|
||||
public String toAsciiString(Long timeoutNanosObject) {
|
||||
long cutoff = 100000000;
|
||||
// Timeout checking is inherently racy. RPCs with timeouts in the past ideally don't even get
|
||||
// here, but if the timeout is expired assume that happened recently and adjust it to the
|
||||
// smallest allowed timeout
|
||||
long timeoutNanos = Math.max(1, timeoutNanosObject);
|
||||
TimeUnit unit = TimeUnit.NANOSECONDS;
|
||||
if (timeoutNanos < 0) {
|
||||
throw new IllegalArgumentException("Timeout too small");
|
||||
} else if (timeoutNanos < cutoff) {
|
||||
if (timeoutNanos < cutoff) {
|
||||
return timeoutNanos + "n";
|
||||
} else if (timeoutNanos < cutoff * 1000L) {
|
||||
return unit.toMicros(timeoutNanos) + "u";
|
||||
|
|
|
@ -1967,6 +1967,9 @@ final class ManagedChannelImpl extends ManagedChannel implements
|
|||
public void requestConnection() {
|
||||
syncContext.throwIfNotInThisSynchronizationContext();
|
||||
checkState(started, "not started");
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
subchannel.obtainActiveTransport();
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ final class PickFirstLoadBalancer extends LoadBalancer {
|
|||
SubchannelPicker picker;
|
||||
switch (newState) {
|
||||
case IDLE:
|
||||
picker = new RequestConnectionPicker(subchannel);
|
||||
picker = new RequestConnectionPicker();
|
||||
break;
|
||||
case CONNECTING:
|
||||
// It's safe to use RequestConnectionPicker here, so when coming from IDLE we could leave
|
||||
|
@ -197,22 +197,12 @@ final class PickFirstLoadBalancer extends LoadBalancer {
|
|||
|
||||
/** Picker that requests connection during the first pick, and returns noResult. */
|
||||
private final class RequestConnectionPicker extends SubchannelPicker {
|
||||
private final Subchannel subchannel;
|
||||
private final AtomicBoolean connectionRequested = new AtomicBoolean(false);
|
||||
|
||||
RequestConnectionPicker(Subchannel subchannel) {
|
||||
this.subchannel = checkNotNull(subchannel, "subchannel");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickResult pickSubchannel(PickSubchannelArgs args) {
|
||||
if (connectionRequested.compareAndSet(false, true)) {
|
||||
helper.getSynchronizationContext().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
subchannel.requestConnection();
|
||||
}
|
||||
});
|
||||
helper.getSynchronizationContext().execute(PickFirstLoadBalancer.this::requestConnection);
|
||||
}
|
||||
return PickResult.withNoResult();
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ public final class ServerImplBuilder extends ServerBuilder<ServerImplBuilder> {
|
|||
ServerCallExecutorSupplier executorSupplier;
|
||||
|
||||
/**
|
||||
* An interface to provide to provide transport specific information for the server. This method
|
||||
* An interface to provide transport specific information for the server. This method
|
||||
* is meant for Transport implementors and should not be used by normal users.
|
||||
*/
|
||||
public interface ClientTransportServersBuilder {
|
||||
|
|
|
@ -465,6 +465,24 @@ public class AbstractClientStreamTest {
|
|||
.isGreaterThan(TimeUnit.MILLISECONDS.toNanos(600));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDeadline_thePastBecomesPositive() {
|
||||
AbstractClientStream.Sink sink = mock(AbstractClientStream.Sink.class);
|
||||
ClientStream stream = new BaseAbstractClientStream(
|
||||
allocator, new BaseTransportState(statsTraceCtx, transportTracer), sink, statsTraceCtx,
|
||||
transportTracer);
|
||||
|
||||
stream.setDeadline(Deadline.after(-1, TimeUnit.NANOSECONDS));
|
||||
stream.start(mockListener);
|
||||
|
||||
ArgumentCaptor<Metadata> headersCaptor = ArgumentCaptor.forClass(Metadata.class);
|
||||
verify(sink).writeHeaders(headersCaptor.capture(), ArgumentMatchers.<byte[]>any());
|
||||
|
||||
Metadata headers = headersCaptor.getValue();
|
||||
assertThat(headers.get(Metadata.Key.of("grpc-timeout", Metadata.ASCII_STRING_MARSHALLER)))
|
||||
.isEqualTo("1n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendTimeoutInsight() {
|
||||
InsightBuilder insight = new InsightBuilder();
|
||||
|
|
|
@ -98,8 +98,8 @@ public class GrpcUtilTest {
|
|||
GrpcUtil.TimeoutMarshaller marshaller =
|
||||
new GrpcUtil.TimeoutMarshaller();
|
||||
// nanos
|
||||
assertEquals("0n", marshaller.toAsciiString(0L));
|
||||
assertEquals(0L, (long) marshaller.parseAsciiString("0n"));
|
||||
assertEquals("1n", marshaller.toAsciiString(1L));
|
||||
assertEquals(1L, (long) marshaller.parseAsciiString("1n"));
|
||||
|
||||
assertEquals("99999999n", marshaller.toAsciiString(99999999L));
|
||||
assertEquals(99999999L, (long) marshaller.parseAsciiString("99999999n"));
|
||||
|
|
|
@ -1767,6 +1767,19 @@ public class ManagedChannelImplTest {
|
|||
any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subchannelsRequestConnectionNoopAfterShutdown() {
|
||||
createChannel();
|
||||
Subchannel sub1 =
|
||||
createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener);
|
||||
|
||||
shutdownSafely(helper, sub1);
|
||||
requestConnectionSafely(helper, sub1);
|
||||
verify(mockTransportFactory, never())
|
||||
.newClientTransport(
|
||||
any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subchannelsNoConnectionShutdownNow() {
|
||||
createChannel();
|
||||
|
|
|
@ -53,6 +53,7 @@ import io.grpc.BinaryLog;
|
|||
import io.grpc.Channel;
|
||||
import io.grpc.Compressor;
|
||||
import io.grpc.Context;
|
||||
import io.grpc.Deadline;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.HandlerRegistry;
|
||||
import io.grpc.IntegerMarshaller;
|
||||
|
@ -1146,11 +1147,21 @@ public class ServerImplTest {
|
|||
@Test
|
||||
public void testContextExpiredBeforeStreamCreate_StreamCancelNotCalledBeforeSetListener()
|
||||
throws Exception {
|
||||
builder.ticker = new Deadline.Ticker() {
|
||||
private long time;
|
||||
|
||||
@Override
|
||||
public long nanoTime() {
|
||||
time += 1000;
|
||||
return time;
|
||||
}
|
||||
};
|
||||
|
||||
AtomicBoolean contextCancelled = new AtomicBoolean(false);
|
||||
AtomicReference<Context> context = new AtomicReference<>();
|
||||
AtomicReference<ServerCall<String, Integer>> callReference = new AtomicReference<>();
|
||||
|
||||
testStreamClose_setup(callReference, context, contextCancelled, 0L);
|
||||
testStreamClose_setup(callReference, context, contextCancelled, 1L);
|
||||
|
||||
// This assert that stream.setListener(jumpListener) is called before stream.cancel(), which
|
||||
// prevents extremely short deadlines causing NPEs.
|
||||
|
|
|
@ -92,7 +92,7 @@ public abstract class AbstractTransportTest {
|
|||
*/
|
||||
public static final int TEST_FLOW_CONTROL_WINDOW = 65 * 1024;
|
||||
|
||||
private static final int TIMEOUT_MS = 5000;
|
||||
protected static final int TIMEOUT_MS = 5000;
|
||||
|
||||
protected static final String GRPC_EXPERIMENTAL_SUPPORT_TRACING_MESSAGE_SIZES =
|
||||
"GRPC_EXPERIMENTAL_SUPPORT_TRACING_MESSAGE_SIZES";
|
||||
|
@ -1449,7 +1449,7 @@ public abstract class AbstractTransportTest {
|
|||
clientStream.flush();
|
||||
clientStream.halfClose();
|
||||
doPingPong(serverListener);
|
||||
assertFalse(serverStreamListener.awaitHalfClosed(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
assertFalse(serverStreamListener.isHalfClosed());
|
||||
|
||||
serverStream.request(1);
|
||||
serverReceived += verifyMessageCountAndClose(serverStreamListener.messageQueue, 1);
|
||||
|
@ -1461,11 +1461,7 @@ public abstract class AbstractTransportTest {
|
|||
Status status = Status.OK.withDescription("... quite a lengthy discussion");
|
||||
serverStream.close(status, new Metadata());
|
||||
doPingPong(serverListener);
|
||||
try {
|
||||
clientStreamListener.awaitClose(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
fail("Expected TimeoutException");
|
||||
} catch (TimeoutException expectedException) {
|
||||
}
|
||||
assertFalse(clientStreamListener.isClosed());
|
||||
|
||||
clientStream.request(1);
|
||||
clientReceived += verifyMessageCountAndClose(clientStreamListener.messageQueue, 1);
|
||||
|
@ -2167,7 +2163,7 @@ public abstract class AbstractTransportTest {
|
|||
return true;
|
||||
}
|
||||
|
||||
private static void runIfNotNull(Runnable runnable) {
|
||||
protected static void runIfNotNull(Runnable runnable) {
|
||||
if (runnable != null) {
|
||||
runnable.run();
|
||||
}
|
||||
|
|
|
@ -43,6 +43,13 @@ public class ClientStreamListenerBase implements ClientStreamListener {
|
|||
return status.get(timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if {@code #awaitClose} would return immediately with a status.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return status.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns response headers from the server or throws {@link
|
||||
* java.util.concurrent.TimeoutException} if they aren't delivered before the timeout.
|
||||
|
|
|
@ -54,6 +54,10 @@ public class ServerStreamListenerBase implements ServerStreamListener {
|
|||
return halfClosedLatch.await(timeout, unit);
|
||||
}
|
||||
|
||||
public boolean isHalfClosed() {
|
||||
return halfClosedLatch.getCount() == 0;
|
||||
}
|
||||
|
||||
public Status awaitClose(int timeout, TimeUnit unit) throws Exception {
|
||||
return status.get(timeout, unit);
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ repositories {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace 'io.grpc.cronet'
|
||||
namespace = 'io.grpc.cronet'
|
||||
compileSdkVersion 33
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5")
|
||||
bazel_dep(name = "grpc-java", repo_name = "io_grpc_grpc_java", version = "1.74.0-SNAPSHOT") # CURRENT_GRPC_VERSION
|
||||
bazel_dep(name = "grpc-java", repo_name = "io_grpc_grpc_java", version = "1.76.0-SNAPSHOT") # CURRENT_GRPC_VERSION
|
||||
bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58")
|
||||
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
|
||||
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
||||
|
@ -11,10 +10,6 @@ local_path_override(
|
|||
path = "..",
|
||||
)
|
||||
|
||||
switched_rules = use_extension("@com_google_googleapis//:extensions.bzl", "switched_rules")
|
||||
|
||||
switched_rules.use_languages(java = True)
|
||||
|
||||
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||
|
||||
use_repo(maven, "maven")
|
||||
|
|
|
@ -28,6 +28,10 @@ load("@io_grpc_grpc_java//:repositories.bzl", "grpc_java_repositories")
|
|||
|
||||
grpc_java_repositories()
|
||||
|
||||
load("@bazel_jar_jar//:jar_jar.bzl", "jar_jar_repositories")
|
||||
|
||||
jar_jar_repositories()
|
||||
|
||||
# Protobuf now requires C++14 or higher, which requires Bazel configuration
|
||||
# outside the WORKSPACE. See .bazelrc in this directory.
|
||||
load("@com_google_protobuf//:protobuf_deps.bzl", "PROTOBUF_MAVEN_ARTIFACTS")
|
||||
|
@ -35,15 +39,10 @@ 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(
|
||||
|
|
|
@ -34,7 +34,7 @@ android {
|
|||
protobuf {
|
||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||
plugins {
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -54,12 +54,12 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
|
||||
// You need to build grpc-java to obtain these libraries below.
|
||||
implementation 'io.grpc:grpc-okhttp:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'com.google.truth:truth:1.1.5'
|
||||
testImplementation 'io.grpc:grpc-testing:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
testImplementation 'io.grpc:grpc-testing:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ android {
|
|||
protobuf {
|
||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||
plugins {
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -52,8 +52,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
|
||||
// You need to build grpc-java to obtain these libraries below.
|
||||
implementation 'io.grpc:grpc-okhttp:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ android {
|
|||
protobuf {
|
||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||
plugins {
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -52,8 +52,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
|
||||
// You need to build grpc-java to obtain these libraries below.
|
||||
implementation 'io.grpc:grpc-okhttp:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ android {
|
|||
protobuf {
|
||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||
plugins {
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -53,8 +53,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
|
||||
// You need to build grpc-java to obtain these libraries below.
|
||||
implementation 'io.grpc:grpc-okhttp:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
def protocVersion = protobufVersion
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
// grpc-alts transitively depends on grpc-netty-shaded, grpc-protobuf, and grpc-stub
|
||||
|
|
|
@ -23,8 +23,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.74.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-debug</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.74.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -23,8 +23,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.74.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-dualstack</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.74.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
def protocVersion = protobufVersion
|
||||
|
||||
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.74.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-gauth</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.74.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.8</protobuf.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -22,10 +22,10 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def openTelemetryVersion = '1.40.0'
|
||||
def openTelemetryPrometheusVersion = '1.40.0-alpha'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
def openTelemetryVersion = '1.52.0'
|
||||
def openTelemetryPrometheusVersion = '1.52.0-alpha'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -22,8 +22,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.74.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-hostname</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.74.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
def protocVersion = protobufVersion
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.74.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-jwt-auth</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.74.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.8</protobuf.version>
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
def protocVersion = protobufVersion
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.74.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-oauth</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.74.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.8</protobuf.version>
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,10 +21,10 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def openTelemetryVersion = '1.40.0'
|
||||
def openTelemetryPrometheusVersion = '1.40.0-alpha'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
def openTelemetryVersion = '1.52.0'
|
||||
def openTelemetryPrometheusVersion = '1.52.0-alpha'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -16,8 +16,8 @@ java {
|
|||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -16,8 +16,8 @@ java {
|
|||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -15,8 +15,8 @@ java {
|
|||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}",
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.74.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-tls</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.74.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.74.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.74.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>examples</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.74.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.8</protobuf.version>
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for JDK 8 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -122,7 +122,7 @@ class ShufflingPickFirstLoadBalancer extends LoadBalancer {
|
|||
SubchannelPicker picker;
|
||||
switch (currentState) {
|
||||
case IDLE:
|
||||
picker = new RequestConnectionPicker(subchannel);
|
||||
picker = new RequestConnectionPicker();
|
||||
break;
|
||||
case CONNECTING:
|
||||
picker = new Picker(PickResult.withNoResult());
|
||||
|
@ -182,22 +182,13 @@ class ShufflingPickFirstLoadBalancer extends LoadBalancer {
|
|||
*/
|
||||
private final class RequestConnectionPicker extends SubchannelPicker {
|
||||
|
||||
private final Subchannel subchannel;
|
||||
private final AtomicBoolean connectionRequested = new AtomicBoolean(false);
|
||||
|
||||
RequestConnectionPicker(Subchannel subchannel) {
|
||||
this.subchannel = checkNotNull(subchannel, "subchannel");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickResult pickSubchannel(PickSubchannelArgs args) {
|
||||
if (connectionRequested.compareAndSet(false, true)) {
|
||||
helper.getSynchronizationContext().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
subchannel.requestConnection();
|
||||
}
|
||||
});
|
||||
helper.getSynchronizationContext().execute(
|
||||
ShufflingPickFirstLoadBalancer.this::requestConnection);
|
||||
}
|
||||
return PickResult.withNoResult();
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
[versions]
|
||||
netty = '4.1.110.Final'
|
||||
netty = '4.1.124.Final'
|
||||
# Keep the following references of tcnative version in sync whenever it's updated:
|
||||
# SECURITY.md
|
||||
nettytcnative = '2.0.70.Final'
|
||||
nettytcnative = '2.0.72.Final'
|
||||
opencensus = "0.31.1"
|
||||
# Not upgrading to 4.x as it is not yet ABI compatible.
|
||||
# https://github.com/protocolbuffers/protobuf/issues/17247
|
||||
protobuf = "3.25.5"
|
||||
protobuf = "3.25.8"
|
||||
|
||||
[libraries]
|
||||
android-annotations = "com.google.android:annotations:4.1.1.4"
|
||||
# androidx-annotation 1.9.1+ uses Kotlin and requires Android Gradle Plugin 9+
|
||||
androidx-annotation = "androidx.annotation:annotation:1.9.0"
|
||||
# 1.15.0 requires libraries and applications that depend on it to compile against
|
||||
# version 35 or later of the Android APIs.
|
||||
androidx-core = "androidx.core:core:1.13.1"
|
||||
# androidx-lifecycle 2.9+ requires Java 17
|
||||
androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.8.7"
|
||||
androidx-lifecycle-service = "androidx.lifecycle:lifecycle-service:2.8.7"
|
||||
androidx-test-core = "androidx.test:core:1.6.1"
|
||||
|
@ -36,13 +38,13 @@ cronet-embedded = "org.chromium.net:cronet-embedded:119.6045.31"
|
|||
errorprone-annotations = "com.google.errorprone:error_prone_annotations:2.30.0"
|
||||
# error-prone 2.32.0+ require Java 17+
|
||||
errorprone-core = "com.google.errorprone:error_prone_core:2.31.0"
|
||||
google-api-protos = "com.google.api.grpc:proto-google-common-protos:2.51.0"
|
||||
google-api-protos = "com.google.api.grpc:proto-google-common-protos:2.59.2"
|
||||
# google-auth-library 1.25.0+ requires error_prone_annotations 2.31.0+, which
|
||||
# breaks the Android build
|
||||
google-auth-credentials = "com.google.auth:google-auth-library-credentials:1.24.1"
|
||||
google-auth-oauth2Http = "com.google.auth:google-auth-library-oauth2-http:1.24.1"
|
||||
# Release notes: https://cloud.google.com/logging/docs/release-notes
|
||||
google-cloud-logging = "com.google.cloud:google-cloud-logging:3.21.2"
|
||||
google-cloud-logging = "com.google.cloud:google-cloud-logging:3.23.1"
|
||||
# 2.12.1 requires error_prone_annotations:2.36.0 but we are stuck with 2.30.0
|
||||
gson = "com.google.code.gson:gson:2.11.0"
|
||||
# 33.4.0 requires com.google.errorprone:error_prone_annotations:2.36.0 but we are stuck with 2.30.0 (see above)
|
||||
|
@ -61,7 +63,7 @@ javax-annotation = "org.apache.tomcat:annotations-api:6.0.53"
|
|||
javax-servlet-api = "javax.servlet:javax.servlet-api:4.0.1"
|
||||
# 12.0.0+ require Java 17+
|
||||
jetty-client = "org.eclipse.jetty:jetty-client:11.0.24"
|
||||
jetty-http2-server = "org.eclipse.jetty.http2:jetty-http2-server:12.0.16"
|
||||
jetty-http2-server = "org.eclipse.jetty.http2:jetty-http2-server:12.0.23"
|
||||
jetty-http2-server10 = "org.eclipse.jetty.http2:http2-server:10.0.20"
|
||||
jetty-servlet = "org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.16"
|
||||
jetty-servlet10 = "org.eclipse.jetty:jetty-servlet:10.0.20"
|
||||
|
@ -91,19 +93,19 @@ opencensus-contrib-grpc-metrics = { module = "io.opencensus:opencensus-contrib-g
|
|||
opencensus-exporter-stats-stackdriver = { module = "io.opencensus:opencensus-exporter-stats-stackdriver", version.ref = "opencensus" }
|
||||
opencensus-exporter-trace-stackdriver = { module = "io.opencensus:opencensus-exporter-trace-stackdriver", version.ref = "opencensus" }
|
||||
opencensus-impl = { module = "io.opencensus:opencensus-impl", version.ref = "opencensus" }
|
||||
opentelemetry-api = "io.opentelemetry:opentelemetry-api:1.46.0"
|
||||
opentelemetry-exporter-prometheus = "io.opentelemetry:opentelemetry-exporter-prometheus:1.46.0-alpha"
|
||||
opentelemetry-gcp-resources = "io.opentelemetry.contrib:opentelemetry-gcp-resources:1.43.0-alpha"
|
||||
opentelemetry-sdk-extension-autoconfigure = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.46.0"
|
||||
opentelemetry-sdk-testing = "io.opentelemetry:opentelemetry-sdk-testing:1.46.0"
|
||||
opentelemetry-api = "io.opentelemetry:opentelemetry-api:1.52.0"
|
||||
opentelemetry-exporter-prometheus = "io.opentelemetry:opentelemetry-exporter-prometheus:1.52.0-alpha"
|
||||
opentelemetry-gcp-resources = "io.opentelemetry.contrib:opentelemetry-gcp-resources:1.48.0-alpha"
|
||||
opentelemetry-sdk-extension-autoconfigure = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.52.0"
|
||||
opentelemetry-sdk-testing = "io.opentelemetry:opentelemetry-sdk-testing:1.52.0"
|
||||
perfmark-api = "io.perfmark:perfmark-api:0.27.0"
|
||||
protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
|
||||
protobuf-java-util = { module = "com.google.protobuf:protobuf-java-util", version.ref = "protobuf" }
|
||||
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
|
||||
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
|
||||
re2j = "com.google.re2j:re2j:1.8"
|
||||
robolectric = "org.robolectric:robolectric:4.14.1"
|
||||
s2a-proto = "com.google.s2a.proto.v2:s2a-proto:0.1.1"
|
||||
robolectric = "org.robolectric:robolectric:4.15.1"
|
||||
s2a-proto = "com.google.s2a.proto.v2:s2a-proto:0.1.2"
|
||||
signature-android = "net.sf.androidscents.signature:android-api-level-21:5.0.1_r2"
|
||||
signature-java = "org.codehaus.mojo.signature:java18:1.0"
|
||||
# 11.0.0+ require Java 17+
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
@ -18,7 +18,6 @@ package io.grpc.inprocess;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
@ -939,8 +938,7 @@ final class InProcessTransport implements ServerTransport, ConnectionClientTrans
|
|||
@Override
|
||||
public void setDeadline(Deadline deadline) {
|
||||
headers.discardAll(TIMEOUT_KEY);
|
||||
long effectiveTimeout = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||
headers.put(TIMEOUT_KEY, effectiveTimeout);
|
||||
headers.put(TIMEOUT_KEY, deadline.timeRemaining(TimeUnit.NANOSECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -39,6 +39,7 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Logger;
|
||||
import org.HdrHistogram.Histogram;
|
||||
|
||||
/**
|
||||
|
@ -48,6 +49,8 @@ import org.HdrHistogram.Histogram;
|
|||
* https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md#rpc_soak
|
||||
*/
|
||||
final class SoakClient {
|
||||
private static final Logger logger = Logger.getLogger(SoakClient.class.getName());
|
||||
|
||||
private static class SoakIterationResult {
|
||||
public SoakIterationResult(long latencyMs, Status status) {
|
||||
this.latencyMs = latencyMs;
|
||||
|
@ -171,7 +174,7 @@ final class SoakClient {
|
|||
iterationsDone += threadResult.getIterationsDone();
|
||||
latencies.add(threadResult.getLatencies());
|
||||
}
|
||||
System.err.println(
|
||||
logger.info(
|
||||
String.format(
|
||||
Locale.US,
|
||||
"(server_uri: %s) soak test ran: %d / %d iterations. total failures: %d. "
|
||||
|
@ -244,14 +247,16 @@ final class SoakClient {
|
|||
if (!result.getStatus().equals(Status.OK)) {
|
||||
threadResults.threadFailures++;
|
||||
logStr.append(String.format(" failed: %s", result.getStatus()));
|
||||
logger.warning(logStr.toString());
|
||||
} else if (result.getLatencyMs() > maxAcceptablePerIterationLatencyMs) {
|
||||
threadResults.threadFailures++;
|
||||
logStr.append(
|
||||
" exceeds max acceptable latency: " + maxAcceptablePerIterationLatencyMs);
|
||||
logger.warning(logStr.toString());
|
||||
} else {
|
||||
logStr.append(" succeeded");
|
||||
logger.info(logStr.toString());
|
||||
}
|
||||
System.err.println(logStr.toString());
|
||||
threadResults.iterationsDone++;
|
||||
threadResults.getLatencies().recordValue(result.getLatencyMs());
|
||||
long remainingNs = earliestNextStartNs - System.nanoTime();
|
||||
|
|
|
@ -147,6 +147,33 @@ _java_grpc_library = rule(
|
|||
implementation = _java_rpc_library_impl,
|
||||
)
|
||||
|
||||
# A copy of _java_grpc_library, except with a neverlink=1 _toolchain
|
||||
INTERNAL_java_grpc_library_for_xds = rule(
|
||||
attrs = {
|
||||
"srcs": attr.label_list(
|
||||
mandatory = True,
|
||||
allow_empty = False,
|
||||
providers = [ProtoInfo],
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
mandatory = True,
|
||||
allow_empty = False,
|
||||
providers = [JavaInfo],
|
||||
),
|
||||
"_toolchain": attr.label(
|
||||
default = Label("//xds:java_grpc_library_toolchain"),
|
||||
),
|
||||
},
|
||||
toolchains = ["@bazel_tools//tools/jdk:toolchain_type"],
|
||||
fragments = ["java"],
|
||||
outputs = {
|
||||
"jar": "lib%{name}.jar",
|
||||
"srcjar": "lib%{name}-src.jar",
|
||||
},
|
||||
provides = [JavaInfo],
|
||||
implementation = _java_rpc_library_impl,
|
||||
)
|
||||
|
||||
_java_lite_grpc_library = rule(
|
||||
attrs = {
|
||||
"srcs": attr.label_list(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue