mirror of https://github.com/grpc/grpc-java.git
Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
|
c7202c0db5 | |
|
028afbe352 | |
|
afdbecb235 | |
|
2039266ebc | |
|
43bef65cf9 | |
|
437e03dc98 | |
|
6462ef9a11 | |
|
95d16d85c8 | |
|
f50726d32e | |
|
06707f7c38 | |
|
efcdebb904 | |
|
f30964ab82 | |
|
7040417eee | |
|
a40c8cf5a4 | |
|
8b46ad58c3 |
33
MODULE.bazel
33
MODULE.bazel
|
@ -2,13 +2,13 @@ module(
|
||||||
name = "grpc-java",
|
name = "grpc-java",
|
||||||
compatibility_level = 0,
|
compatibility_level = 0,
|
||||||
repo_name = "io_grpc_grpc_java",
|
repo_name = "io_grpc_grpc_java",
|
||||||
version = "1.75.0-SNAPSHOT", # CURRENT_GRPC_VERSION
|
version = "1.76.0-SNAPSHOT", # CURRENT_GRPC_VERSION
|
||||||
)
|
)
|
||||||
|
|
||||||
# GRPC_DEPS_START
|
# GRPC_DEPS_START
|
||||||
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||||
"com.google.android:annotations:4.1.1.4",
|
"com.google.android:annotations:4.1.1.4",
|
||||||
"com.google.api.grpc:proto-google-common-protos:2.51.0",
|
"com.google.api.grpc:proto-google-common-protos:2.59.2",
|
||||||
"com.google.auth:google-auth-library-credentials:1.24.1",
|
"com.google.auth:google-auth-library-credentials:1.24.1",
|
||||||
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
||||||
"com.google.auto.value:auto-value-annotations:1.11.0",
|
"com.google.auto.value:auto-value-annotations:1.11.0",
|
||||||
|
@ -19,24 +19,24 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||||
"com.google.guava:failureaccess:1.0.1",
|
"com.google.guava:failureaccess:1.0.1",
|
||||||
"com.google.guava:guava:33.3.1-android",
|
"com.google.guava:guava:33.3.1-android",
|
||||||
"com.google.re2j:re2j:1.8",
|
"com.google.re2j:re2j:1.8",
|
||||||
"com.google.s2a.proto.v2:s2a-proto:0.1.1",
|
"com.google.s2a.proto.v2:s2a-proto:0.1.2",
|
||||||
"com.google.truth:truth:1.4.2",
|
"com.google.truth:truth:1.4.2",
|
||||||
"com.squareup.okhttp:okhttp:2.7.5",
|
"com.squareup.okhttp:okhttp:2.7.5",
|
||||||
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
||||||
"io.netty:netty-buffer:4.1.110.Final",
|
"io.netty:netty-buffer:4.1.124.Final",
|
||||||
"io.netty:netty-codec-http2:4.1.110.Final",
|
"io.netty:netty-codec-http2:4.1.124.Final",
|
||||||
"io.netty:netty-codec-http:4.1.110.Final",
|
"io.netty:netty-codec-http:4.1.124.Final",
|
||||||
"io.netty:netty-codec-socks:4.1.110.Final",
|
"io.netty:netty-codec-socks:4.1.124.Final",
|
||||||
"io.netty:netty-codec:4.1.110.Final",
|
"io.netty:netty-codec:4.1.124.Final",
|
||||||
"io.netty:netty-common:4.1.110.Final",
|
"io.netty:netty-common:4.1.124.Final",
|
||||||
"io.netty:netty-handler-proxy:4.1.110.Final",
|
"io.netty:netty-handler-proxy:4.1.124.Final",
|
||||||
"io.netty:netty-handler:4.1.110.Final",
|
"io.netty:netty-handler:4.1.124.Final",
|
||||||
"io.netty:netty-resolver:4.1.110.Final",
|
"io.netty:netty-resolver:4.1.124.Final",
|
||||||
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
||||||
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
||||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final",
|
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.124.Final",
|
||||||
"io.netty:netty-transport-native-unix-common:4.1.110.Final",
|
"io.netty:netty-transport-native-unix-common:4.1.124.Final",
|
||||||
"io.netty:netty-transport:4.1.110.Final",
|
"io.netty:netty-transport:4.1.124.Final",
|
||||||
"io.opencensus:opencensus-api:0.31.0",
|
"io.opencensus:opencensus-api:0.31.0",
|
||||||
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
||||||
"io.perfmark:perfmark-api:0.27.0",
|
"io.perfmark:perfmark-api:0.27.0",
|
||||||
|
@ -50,7 +50,8 @@ bazel_dep(name = "bazel_jar_jar", version = "0.1.7")
|
||||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||||
bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5")
|
bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5")
|
||||||
bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58")
|
bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58")
|
||||||
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
|
# Protobuf 25.5+ is incompatible with Bazel 7 with bzlmod
|
||||||
|
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "24.4")
|
||||||
bazel_dep(name = "rules_cc", version = "0.0.9")
|
bazel_dep(name = "rules_cc", version = "0.0.9")
|
||||||
bazel_dep(name = "rules_java", version = "5.3.5")
|
bazel_dep(name = "rules_java", version = "5.3.5")
|
||||||
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
||||||
|
|
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
|
guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC
|
||||||
basics](https://grpc.io/docs/languages/java/basics).
|
basics](https://grpc.io/docs/languages/java/basics).
|
||||||
|
|
||||||
The [examples](https://github.com/grpc/grpc-java/tree/v1.74.0/examples) and the
|
The [examples](https://github.com/grpc/grpc-java/tree/v1.75.0/examples) and the
|
||||||
[Android example](https://github.com/grpc/grpc-java/tree/v1.74.0/examples/android)
|
[Android example](https://github.com/grpc/grpc-java/tree/v1.75.0/examples/android)
|
||||||
are standalone projects that showcase the usage of gRPC.
|
are standalone projects that showcase the usage of gRPC.
|
||||||
|
|
||||||
Download
|
Download
|
||||||
|
@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.grpc</groupId>
|
<groupId>io.grpc</groupId>
|
||||||
<artifactId>grpc-netty-shaded</artifactId>
|
<artifactId>grpc-netty-shaded</artifactId>
|
||||||
<version>1.74.0</version>
|
<version>1.75.0</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.grpc</groupId>
|
<groupId>io.grpc</groupId>
|
||||||
<artifactId>grpc-protobuf</artifactId>
|
<artifactId>grpc-protobuf</artifactId>
|
||||||
<version>1.74.0</version>
|
<version>1.75.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.grpc</groupId>
|
<groupId>io.grpc</groupId>
|
||||||
<artifactId>grpc-stub</artifactId>
|
<artifactId>grpc-stub</artifactId>
|
||||||
<version>1.74.0</version>
|
<version>1.75.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency> <!-- necessary for Java 9+ -->
|
<dependency> <!-- necessary for Java 9+ -->
|
||||||
<groupId>org.apache.tomcat</groupId>
|
<groupId>org.apache.tomcat</groupId>
|
||||||
|
@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
||||||
|
|
||||||
Or for Gradle with non-Android, add to your dependencies:
|
Or for Gradle with non-Android, add to your dependencies:
|
||||||
```gradle
|
```gradle
|
||||||
runtimeOnly 'io.grpc:grpc-netty-shaded:1.74.0'
|
runtimeOnly 'io.grpc:grpc-netty-shaded:1.75.0'
|
||||||
implementation 'io.grpc:grpc-protobuf:1.74.0'
|
implementation 'io.grpc:grpc-protobuf:1.75.0'
|
||||||
implementation 'io.grpc:grpc-stub:1.74.0'
|
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||||
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
||||||
```
|
```
|
||||||
|
|
||||||
For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and
|
For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and
|
||||||
`grpc-protobuf-lite` instead of `grpc-protobuf`:
|
`grpc-protobuf-lite` instead of `grpc-protobuf`:
|
||||||
```gradle
|
```gradle
|
||||||
implementation 'io.grpc:grpc-okhttp:1.74.0'
|
implementation 'io.grpc:grpc-okhttp:1.75.0'
|
||||||
implementation 'io.grpc:grpc-protobuf-lite:1.74.0'
|
implementation 'io.grpc:grpc-protobuf-lite:1.75.0'
|
||||||
implementation 'io.grpc:grpc-stub:1.74.0'
|
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||||
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -99,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).
|
(with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below).
|
||||||
|
|
||||||
[the JARs]:
|
[the JARs]:
|
||||||
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.74.0
|
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.75.0
|
||||||
|
|
||||||
Development snapshots are available in [Sonatypes's snapshot
|
Development snapshots are available in [Sonatypes's snapshot
|
||||||
repository](https://central.sonatype.com/repository/maven-snapshots/).
|
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>
|
<configuration>
|
||||||
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
|
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
|
||||||
<pluginId>grpc-java</pluginId>
|
<pluginId>grpc-java</pluginId>
|
||||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.74.0:exe:${os.detected.classifier}</pluginArtifact>
|
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.75.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
@ -161,7 +161,7 @@ protobuf {
|
||||||
}
|
}
|
||||||
plugins {
|
plugins {
|
||||||
grpc {
|
grpc {
|
||||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.74.0'
|
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
|
@ -194,7 +194,7 @@ protobuf {
|
||||||
}
|
}
|
||||||
plugins {
|
plugins {
|
||||||
grpc {
|
grpc {
|
||||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.74.0'
|
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
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.59.x | 4.1.97.Final | 2.0.61.Final
|
||||||
1.60.x-1.66.x | 4.1.100.Final | 2.0.61.Final
|
1.60.x-1.66.x | 4.1.100.Final | 2.0.61.Final
|
||||||
1.67.x-1.70.x | 4.1.110.Final | 2.0.65.Final
|
1.67.x-1.70.x | 4.1.110.Final | 2.0.65.Final
|
||||||
1.71.x- | 4.1.110.Final | 2.0.70.Final
|
1.71.x-1.74.x | 4.1.110.Final | 2.0.70.Final
|
||||||
|
1.75.x- | 4.1.124.Final | 2.0.72.Final
|
||||||
|
|
||||||
_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
|
_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ android {
|
||||||
}
|
}
|
||||||
compileSdkVersion 34
|
compileSdkVersion 34
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 22
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
|
@ -217,7 +217,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
||||||
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
||||||
unregisterRunnable =
|
unregisterRunnable =
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
||||||
|
@ -231,7 +230,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
||||||
context.registerReceiver(networkReceiver, networkIntentFilter);
|
context.registerReceiver(networkReceiver, networkIntentFilter);
|
||||||
unregisterRunnable =
|
unregisterRunnable =
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
context.unregisterReceiver(networkReceiver);
|
context.unregisterReceiver(networkReceiver);
|
||||||
|
|
|
@ -166,6 +166,11 @@ public final class NameResolverRegistry {
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
|
logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
list.add(Class.forName("io.grpc.binder.internal.IntentNameResolverProvider"));
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
logger.log(Level.FINE, "Unable to find IntentNameResolverProvider", e);
|
||||||
|
}
|
||||||
return Collections.unmodifiableList(list);
|
return Collections.unmodifiableList(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ android {
|
||||||
targetCompatibility 1.8
|
targetCompatibility 1.8
|
||||||
}
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 22
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
|
@ -11,11 +11,13 @@
|
||||||
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
|
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="action1"/>
|
<action android:name="action1"/>
|
||||||
|
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service android:name="io.grpc.binder.HostServices$HostService2" android:exported="false">
|
<service android:name="io.grpc.binder.HostServices$HostService2" android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="action2"/>
|
<action android:name="action2"/>
|
||||||
|
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
|
@ -23,6 +23,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
@ -39,7 +40,6 @@ import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
||||||
import io.grpc.ManagedChannel;
|
import io.grpc.ManagedChannel;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
import io.grpc.MethodDescriptor;
|
import io.grpc.MethodDescriptor;
|
||||||
import io.grpc.NameResolverRegistry;
|
|
||||||
import io.grpc.ServerCall;
|
import io.grpc.ServerCall;
|
||||||
import io.grpc.ServerCall.Listener;
|
import io.grpc.ServerCall.Listener;
|
||||||
import io.grpc.ServerCallHandler;
|
import io.grpc.ServerCallHandler;
|
||||||
|
@ -49,7 +49,6 @@ import io.grpc.ServerServiceDefinition;
|
||||||
import io.grpc.Status.Code;
|
import io.grpc.Status.Code;
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
import io.grpc.internal.GrpcUtil;
|
import io.grpc.internal.GrpcUtil;
|
||||||
import io.grpc.internal.testing.FakeNameResolverProvider;
|
|
||||||
import io.grpc.stub.ClientCalls;
|
import io.grpc.stub.ClientCalls;
|
||||||
import io.grpc.stub.MetadataUtils;
|
import io.grpc.stub.MetadataUtils;
|
||||||
import io.grpc.stub.ServerCalls;
|
import io.grpc.stub.ServerCalls;
|
||||||
|
@ -77,7 +76,6 @@ public final class BinderChannelSmokeTest {
|
||||||
|
|
||||||
private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100;
|
private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100;
|
||||||
private static final String MSG = "Some text which will be repeated many many times";
|
private static final String MSG = "Some text which will be repeated many many times";
|
||||||
private static final String SERVER_TARGET_URI = "fake://server";
|
|
||||||
private static final Metadata.Key<PoisonParcelable> POISON_KEY =
|
private static final Metadata.Key<PoisonParcelable> POISON_KEY =
|
||||||
ParcelableUtils.metadataKey("poison-bin", PoisonParcelable.CREATOR);
|
ParcelableUtils.metadataKey("poison-bin", PoisonParcelable.CREATOR);
|
||||||
|
|
||||||
|
@ -99,7 +97,6 @@ public final class BinderChannelSmokeTest {
|
||||||
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
|
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
FakeNameResolverProvider fakeNameResolverProvider;
|
|
||||||
ManagedChannel channel;
|
ManagedChannel channel;
|
||||||
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
|
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
|
||||||
AtomicReference<PeerUid> clientUidCapture = new AtomicReference<>();
|
AtomicReference<PeerUid> clientUidCapture = new AtomicReference<>();
|
||||||
|
@ -138,8 +135,6 @@ public final class BinderChannelSmokeTest {
|
||||||
PeerUids.newPeerIdentifyingServerInterceptor());
|
PeerUids.newPeerIdentifyingServerInterceptor());
|
||||||
|
|
||||||
AndroidComponentAddress serverAddress = HostServices.allocateService(appContext);
|
AndroidComponentAddress serverAddress = HostServices.allocateService(appContext);
|
||||||
fakeNameResolverProvider = new FakeNameResolverProvider(SERVER_TARGET_URI, serverAddress);
|
|
||||||
NameResolverRegistry.getDefaultRegistry().register(fakeNameResolverProvider);
|
|
||||||
HostServices.configureService(
|
HostServices.configureService(
|
||||||
serverAddress,
|
serverAddress,
|
||||||
HostServices.serviceParamsBuilder()
|
HostServices.serviceParamsBuilder()
|
||||||
|
@ -166,7 +161,6 @@ public final class BinderChannelSmokeTest {
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
channel.shutdownNow();
|
channel.shutdownNow();
|
||||||
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider);
|
|
||||||
HostServices.awaitServiceShutdown();
|
HostServices.awaitServiceShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +229,11 @@ public final class BinderChannelSmokeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConnectViaTargetUri() throws Exception {
|
public void testConnectViaTargetUri() throws Exception {
|
||||||
channel = BinderChannelBuilder.forTarget(SERVER_TARGET_URI, appContext).build();
|
// Compare with the <intent-filter> mapping in AndroidManifest.xml.
|
||||||
|
channel =
|
||||||
|
BinderChannelBuilder.forTarget(
|
||||||
|
"intent://authority/path#Intent;action=action1;scheme=scheme;end;", appContext)
|
||||||
|
.build();
|
||||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +243,10 @@ public final class BinderChannelSmokeTest {
|
||||||
channel =
|
channel =
|
||||||
BinderChannelBuilder.forAddress(
|
BinderChannelBuilder.forAddress(
|
||||||
AndroidComponentAddress.forBindIntent(
|
AndroidComponentAddress.forBindIntent(
|
||||||
new Intent().setAction("action1").setPackage(appContext.getPackageName())),
|
new Intent()
|
||||||
|
.setAction("action1")
|
||||||
|
.setData(Uri.parse("scheme://authority/path"))
|
||||||
|
.setPackage(appContext.getPackageName())),
|
||||||
appContext)
|
appContext)
|
||||||
.build();
|
.build();
|
||||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||||
|
|
|
@ -100,7 +100,7 @@ public final class BinderClientTransportTest {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
AndroidComponentAddress serverAddress;
|
AndroidComponentAddress serverAddress;
|
||||||
BinderTransport.BinderClientTransport transport;
|
BinderClientTransport transport;
|
||||||
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
|
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
|
||||||
|
|
||||||
private final ObjectPool<ScheduledExecutorService> executorServicePool =
|
private final ObjectPool<ScheduledExecutorService> executorServicePool =
|
||||||
|
@ -178,7 +178,7 @@ public final class BinderClientTransportTest {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinderTransport.BinderClientTransport build() {
|
public BinderClientTransport build() {
|
||||||
return factoryBuilder
|
return factoryBuilder
|
||||||
.buildClientTransportFactory()
|
.buildClientTransportFactory()
|
||||||
.newClientTransport(serverAddress, new ClientTransportOptions(), null);
|
.newClientTransport(serverAddress, new ClientTransportOptions(), null);
|
||||||
|
@ -502,8 +502,7 @@ public final class BinderClientTransportTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void startAndAwaitReady(
|
private static void startAndAwaitReady(
|
||||||
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
|
BinderClientTransport transport, TestTransportListener transportListener) throws Exception {
|
||||||
throws Exception {
|
|
||||||
transport.start(transportListener).run();
|
transport.start(transportListener).run();
|
||||||
transportListener.awaitReady();
|
transportListener.awaitReady();
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,8 +106,7 @@ public final class BinderTransportTest extends AbstractTransportTest {
|
||||||
options.setEagAttributes(eagAttrs());
|
options.setEagAttributes(eagAttrs());
|
||||||
options.setChannelLogger(transportLogger());
|
options.setChannelLogger(transportLogger());
|
||||||
|
|
||||||
return new BinderTransport.BinderClientTransport(
|
return new BinderClientTransport(builder.buildClientTransportFactory(), addr, options);
|
||||||
builder.buildClientTransportFactory(), addr, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -34,6 +34,18 @@ public final class ApiConstants {
|
||||||
*/
|
*/
|
||||||
public static final String ACTION_BIND = "grpc.io.action.BIND";
|
public static final String ACTION_BIND = "grpc.io.action.BIND";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives a {@link NameResolver} access to its Channel's "source" {@link android.content.Context},
|
||||||
|
* the entry point to almost every other Android API.
|
||||||
|
*
|
||||||
|
* <p>This argument is set automatically by {@link BinderChannelBuilder}. Any value passed to
|
||||||
|
* {@link io.grpc.ManagedChannelBuilder#setNameResolverArg} will be ignored.
|
||||||
|
*
|
||||||
|
* <p>See {@link BinderChannelBuilder#forTarget(String, android.content.Context)} for more.
|
||||||
|
*/
|
||||||
|
public static final NameResolver.Args.Key<android.content.Context> SOURCE_ANDROID_CONTEXT =
|
||||||
|
NameResolver.Args.Key.create("source-android-context");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the Android user in which target URIs should be resolved.
|
* Specifies the Android user in which target URIs should be resolved.
|
||||||
*
|
*
|
||||||
|
|
|
@ -67,4 +67,25 @@ public abstract class AsyncSecurityPolicy extends SecurityPolicy {
|
||||||
* authorized.
|
* authorized.
|
||||||
*/
|
*/
|
||||||
public abstract ListenableFuture<Status> checkAuthorizationAsync(int uid);
|
public abstract ListenableFuture<Status> checkAuthorizationAsync(int uid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether the given Android UID is authorized, without providing its raw integer value.
|
||||||
|
*
|
||||||
|
* <p>Calling this is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
|
||||||
|
* the caller provides a {@link PeerUid} wrapper instead of the raw integer uid (known only to the
|
||||||
|
* transport). This allows a server to check additional application-layer security policy for
|
||||||
|
* itself *after* the call itself is authorized by the transport layer. Cross cutting application-
|
||||||
|
* layer checks could be done from a {@link io.grpc.ServerInterceptor}. Checks based on the
|
||||||
|
* substance of a request message could be done by the individual RPC method implementations
|
||||||
|
* themselves.
|
||||||
|
*
|
||||||
|
* <p>See #checkAuthorizationAsync(int) for details on the semantics. See {@link
|
||||||
|
* PeerUids#newPeerIdentifyingServerInterceptor()} for how to get a {@link PeerUid}.
|
||||||
|
*
|
||||||
|
* @param uid The Android UID to authenticate.
|
||||||
|
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||||
|
*/
|
||||||
|
public final ListenableFuture<Status> checkAuthorizationAsync(PeerUid uid) {
|
||||||
|
return checkAuthorizationAsync(uid.getUid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -321,6 +321,8 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
||||||
public ManagedChannel build() {
|
public ManagedChannel build() {
|
||||||
transportFactoryBuilder.setOffloadExecutorPool(
|
transportFactoryBuilder.setOffloadExecutorPool(
|
||||||
managedChannelImplBuilder.getOffloadExecutorPool());
|
managedChannelImplBuilder.getOffloadExecutorPool());
|
||||||
|
setNameResolverArg(
|
||||||
|
ApiConstants.SOURCE_ANDROID_CONTEXT, transportFactoryBuilder.getSourceContext());
|
||||||
return super.build();
|
return super.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,6 @@ public final class SecurityPolicies {
|
||||||
* Creates {@link SecurityPolicy} which checks if the app is a device owner app. See {@link
|
* Creates {@link SecurityPolicy} which checks if the app is a device owner app. See {@link
|
||||||
* DevicePolicyManager}.
|
* DevicePolicyManager}.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(18)
|
|
||||||
public static io.grpc.binder.SecurityPolicy isDeviceOwner(Context applicationContext) {
|
public static io.grpc.binder.SecurityPolicy isDeviceOwner(Context applicationContext) {
|
||||||
DevicePolicyManager devicePolicyManager =
|
DevicePolicyManager devicePolicyManager =
|
||||||
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||||
|
@ -199,7 +198,6 @@ public final class SecurityPolicies {
|
||||||
* Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See {@link
|
* Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See {@link
|
||||||
* DevicePolicyManager}.
|
* DevicePolicyManager}.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(21)
|
|
||||||
public static SecurityPolicy isProfileOwner(Context applicationContext) {
|
public static SecurityPolicy isProfileOwner(Context applicationContext) {
|
||||||
DevicePolicyManager devicePolicyManager =
|
DevicePolicyManager devicePolicyManager =
|
||||||
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||||
|
|
|
@ -53,4 +53,25 @@ public abstract class SecurityPolicy {
|
||||||
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||||
*/
|
*/
|
||||||
public abstract Status checkAuthorization(int uid);
|
public abstract Status checkAuthorization(int uid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether the given Android UID is authorized, without providing its raw integer value.
|
||||||
|
*
|
||||||
|
* <p>Calling this is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
|
||||||
|
* the caller provides a {@link PeerUid} wrapper instead of the raw integer uid (known only to the
|
||||||
|
* transport). This allows a server to check additional application-layer security policy for
|
||||||
|
* itself *after* the call itself is authorized by the transport layer. Cross cutting application-
|
||||||
|
* layer checks could be done from a {@link io.grpc.ServerInterceptor}. Checks based on the
|
||||||
|
* substance of a request message could be done by the individual RPC method implementations
|
||||||
|
* themselves.
|
||||||
|
*
|
||||||
|
* <p>See #checkAuthorizationAsync(int) for details on the semantics. See {@link
|
||||||
|
* PeerUids#newPeerIdentifyingServerInterceptor()} for how to get a {@link PeerUid}.
|
||||||
|
*
|
||||||
|
* @param uid The Android UID to authenticate.
|
||||||
|
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||||
|
*/
|
||||||
|
public final Status checkAuthorization(PeerUid uid) {
|
||||||
|
return checkAuthorization(uid.getUid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import io.grpc.internal.ServerTransport;
|
||||||
import io.grpc.internal.ServerTransportListener;
|
import io.grpc.internal.ServerTransportListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks which {@link BinderTransport.BinderServerTransport} are currently active and allows
|
* Tracks which {@link BinderServerTransport} are currently active and allows invoking a {@link
|
||||||
* invoking a {@link Runnable} only once all transports are terminated.
|
* Runnable} only once all transports are terminated.
|
||||||
*/
|
*/
|
||||||
final class ActiveTransportTracker implements ServerListener {
|
final class ActiveTransportTracker implements ServerListener {
|
||||||
private final ServerListener delegate;
|
private final ServerListener delegate;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,12 +83,12 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BinderTransport.BinderClientTransport newClientTransport(
|
public BinderClientTransport newClientTransport(
|
||||||
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
|
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("The transport factory is closed.");
|
throw new IllegalStateException("The transport factory is closed.");
|
||||||
}
|
}
|
||||||
return new BinderTransport.BinderClientTransport(this, (AndroidComponentAddress) addr, options);
|
return new BinderClientTransport(this, (AndroidComponentAddress) addr, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -142,6 +142,10 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Context getSourceContext() {
|
||||||
|
return sourceContext;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setOffloadExecutorPool(ObjectPool<? extends Executor> offloadExecutorPool) {
|
public Builder setOffloadExecutorPool(ObjectPool<? extends Executor> offloadExecutorPool) {
|
||||||
this.offloadExecutorPool = checkNotNull(offloadExecutorPool, "offloadExecutorPool");
|
this.offloadExecutorPool = checkNotNull(offloadExecutorPool, "offloadExecutorPool");
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -178,8 +178,8 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder.
|
||||||
serverPolicyChecker,
|
serverPolicyChecker,
|
||||||
checkNotNull(executor, "Not started?"));
|
checkNotNull(executor, "Not started?"));
|
||||||
// Create a new transport and let our listener know about it.
|
// Create a new transport and let our listener know about it.
|
||||||
BinderTransport.BinderServerTransport transport =
|
BinderServerTransport transport =
|
||||||
new BinderTransport.BinderServerTransport(
|
new BinderServerTransport(
|
||||||
executorServicePool,
|
executorServicePool,
|
||||||
attrsBuilder.build(),
|
attrsBuilder.build(),
|
||||||
streamTracerFactories,
|
streamTracerFactories,
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
|
import io.grpc.Attributes;
|
||||||
|
import io.grpc.Grpc;
|
||||||
|
import io.grpc.Internal;
|
||||||
|
import io.grpc.InternalLogId;
|
||||||
|
import io.grpc.Metadata;
|
||||||
|
import io.grpc.ServerStreamTracer;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.internal.ObjectPool;
|
||||||
|
import io.grpc.internal.ServerStream;
|
||||||
|
import io.grpc.internal.ServerTransport;
|
||||||
|
import io.grpc.internal.ServerTransportListener;
|
||||||
|
import io.grpc.internal.StatsTraceContext;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Concrete server-side transport implementation. */
|
||||||
|
@Internal
|
||||||
|
public final class BinderServerTransport extends BinderTransport implements ServerTransport {
|
||||||
|
|
||||||
|
private final List<ServerStreamTracer.Factory> streamTracerFactories;
|
||||||
|
@Nullable private ServerTransportListener serverTransportListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new transport instance.
|
||||||
|
*
|
||||||
|
* @param binderDecorator used to decorate 'callbackBinder', for fault injection.
|
||||||
|
*/
|
||||||
|
public BinderServerTransport(
|
||||||
|
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||||
|
Attributes attributes,
|
||||||
|
List<ServerStreamTracer.Factory> streamTracerFactories,
|
||||||
|
OneWayBinderProxy.Decorator binderDecorator,
|
||||||
|
IBinder callbackBinder) {
|
||||||
|
super(executorServicePool, attributes, binderDecorator, buildLogId(attributes));
|
||||||
|
this.streamTracerFactories = streamTracerFactories;
|
||||||
|
// TODO(jdcormie): Plumb in the Server's executor() and use it here instead.
|
||||||
|
setOutgoingBinder(OneWayBinderProxy.wrap(callbackBinder, getScheduledExecutorService()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setServerTransportListener(
|
||||||
|
ServerTransportListener serverTransportListener) {
|
||||||
|
this.serverTransportListener = serverTransportListener;
|
||||||
|
if (isShutdown()) {
|
||||||
|
setState(TransportState.SHUTDOWN_TERMINATED);
|
||||||
|
notifyTerminated();
|
||||||
|
releaseExecutors();
|
||||||
|
} else {
|
||||||
|
sendSetupTransaction();
|
||||||
|
// Check we're not shutdown again, since a failure inside sendSetupTransaction (or a callback
|
||||||
|
// it triggers), could have shut us down.
|
||||||
|
if (!isShutdown()) {
|
||||||
|
setState(TransportState.READY);
|
||||||
|
attributes = serverTransportListener.transportReady(attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatsTraceContext createStatsTraceContext(String methodName, Metadata headers) {
|
||||||
|
return StatsTraceContext.newServerContext(streamTracerFactories, methodName, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized Status startStream(ServerStream stream, String methodName, Metadata headers) {
|
||||||
|
if (isShutdown()) {
|
||||||
|
return Status.UNAVAILABLE.withDescription("transport is shutdown");
|
||||||
|
} else {
|
||||||
|
serverTransportListener.streamCreated(stream, methodName, headers);
|
||||||
|
return Status.OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GuardedBy("this")
|
||||||
|
void notifyShutdown(Status status) {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GuardedBy("this")
|
||||||
|
void notifyTerminated() {
|
||||||
|
if (serverTransportListener != null) {
|
||||||
|
serverTransportListener.transportTerminated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void shutdown() {
|
||||||
|
shutdownInternal(Status.OK, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void shutdownNow(Status reason) {
|
||||||
|
shutdownInternal(reason, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
@GuardedBy("this")
|
||||||
|
protected Inbound<?> createInbound(int callId) {
|
||||||
|
return new Inbound.ServerInbound(this, attributes, callId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InternalLogId buildLogId(Attributes attributes) {
|
||||||
|
return InternalLogId.allocate(
|
||||||
|
BinderServerTransport.class, "from " + attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,67 +19,32 @@ package io.grpc.binder.internal;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
||||||
import static 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.DeadObjectException;
|
import android.os.DeadObjectException;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Process;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.TransactionTooLargeException;
|
import android.os.TransactionTooLargeException;
|
||||||
import androidx.annotation.BinderThread;
|
import androidx.annotation.BinderThread;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Ticker;
|
|
||||||
import com.google.common.base.Verify;
|
import com.google.common.base.Verify;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.errorprone.annotations.CheckReturnValue;
|
|
||||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.CallOptions;
|
|
||||||
import io.grpc.ClientStreamTracer;
|
|
||||||
import io.grpc.Grpc;
|
|
||||||
import io.grpc.Internal;
|
import io.grpc.Internal;
|
||||||
import io.grpc.InternalChannelz.SocketStats;
|
import io.grpc.InternalChannelz.SocketStats;
|
||||||
import io.grpc.InternalLogId;
|
import io.grpc.InternalLogId;
|
||||||
import io.grpc.Metadata;
|
|
||||||
import io.grpc.MethodDescriptor;
|
|
||||||
import io.grpc.SecurityLevel;
|
|
||||||
import io.grpc.ServerStreamTracer;
|
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.StatusException;
|
import io.grpc.StatusException;
|
||||||
import io.grpc.binder.AndroidComponentAddress;
|
|
||||||
import io.grpc.binder.AsyncSecurityPolicy;
|
|
||||||
import io.grpc.binder.InboundParcelablePolicy;
|
import io.grpc.binder.InboundParcelablePolicy;
|
||||||
import io.grpc.binder.SecurityPolicy;
|
|
||||||
import io.grpc.internal.ClientStream;
|
|
||||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
|
||||||
import io.grpc.internal.ConnectionClientTransport;
|
|
||||||
import io.grpc.internal.FailingClientStream;
|
|
||||||
import io.grpc.internal.GrpcAttributes;
|
|
||||||
import io.grpc.internal.GrpcUtil;
|
|
||||||
import io.grpc.internal.ManagedClientTransport;
|
|
||||||
import io.grpc.internal.ObjectPool;
|
import io.grpc.internal.ObjectPool;
|
||||||
import io.grpc.internal.ServerStream;
|
|
||||||
import io.grpc.internal.ServerTransport;
|
|
||||||
import io.grpc.internal.ServerTransportListener;
|
|
||||||
import io.grpc.internal.StatsTraceContext;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -169,10 +134,10 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
|
||||||
private static final int RESERVED_TRANSACTIONS = 1000;
|
private static final int RESERVED_TRANSACTIONS = 1000;
|
||||||
|
|
||||||
/** The first call ID we can use. */
|
/** The first call ID we can use. */
|
||||||
private static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
|
static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
|
||||||
|
|
||||||
/** The last call ID we can use. */
|
/** The last call ID we can use. */
|
||||||
private static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
|
static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
|
||||||
|
|
||||||
/** The states of this transport. */
|
/** The states of this transport. */
|
||||||
protected enum TransportState {
|
protected enum TransportState {
|
||||||
|
@ -218,7 +183,7 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
|
||||||
// Only read/written on @BinderThread.
|
// Only read/written on @BinderThread.
|
||||||
private long acknowledgedIncomingBytes;
|
private long acknowledgedIncomingBytes;
|
||||||
|
|
||||||
private BinderTransport(
|
protected BinderTransport(
|
||||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||||
Attributes attributes,
|
Attributes attributes,
|
||||||
OneWayBinderProxy.Decorator binderDecorator,
|
OneWayBinderProxy.Decorator binderDecorator,
|
||||||
|
@ -559,478 +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;
|
|
||||||
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(ManagedClientTransport.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Concrete server-side transport implementation. */
|
|
||||||
@Internal
|
|
||||||
public static final class BinderServerTransport extends BinderTransport
|
|
||||||
implements ServerTransport {
|
|
||||||
|
|
||||||
private final List<ServerStreamTracer.Factory> streamTracerFactories;
|
|
||||||
@Nullable private ServerTransportListener serverTransportListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new transport instance.
|
|
||||||
*
|
|
||||||
* @param binderDecorator used to decorate 'callbackBinder', for fault injection.
|
|
||||||
*/
|
|
||||||
public BinderServerTransport(
|
|
||||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
|
||||||
Attributes attributes,
|
|
||||||
List<ServerStreamTracer.Factory> streamTracerFactories,
|
|
||||||
OneWayBinderProxy.Decorator binderDecorator,
|
|
||||||
IBinder callbackBinder) {
|
|
||||||
super(executorServicePool, attributes, binderDecorator, buildLogId(attributes));
|
|
||||||
this.streamTracerFactories = streamTracerFactories;
|
|
||||||
// TODO(jdcormie): Plumb in the Server's executor() and use it here instead.
|
|
||||||
setOutgoingBinder(OneWayBinderProxy.wrap(callbackBinder, getScheduledExecutorService()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void setServerTransportListener(
|
|
||||||
ServerTransportListener serverTransportListener) {
|
|
||||||
this.serverTransportListener = serverTransportListener;
|
|
||||||
if (isShutdown()) {
|
|
||||||
setState(TransportState.SHUTDOWN_TERMINATED);
|
|
||||||
notifyTerminated();
|
|
||||||
releaseExecutors();
|
|
||||||
} else {
|
|
||||||
sendSetupTransaction();
|
|
||||||
// Check we're not shutdown again, since a failure inside sendSetupTransaction (or a
|
|
||||||
// callback it triggers), could have shut us down.
|
|
||||||
if (!isShutdown()) {
|
|
||||||
setState(TransportState.READY);
|
|
||||||
attributes = serverTransportListener.transportReady(attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StatsTraceContext createStatsTraceContext(String methodName, Metadata headers) {
|
|
||||||
return StatsTraceContext.newServerContext(streamTracerFactories, methodName, headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized Status startStream(ServerStream stream, String methodName, Metadata headers) {
|
|
||||||
if (isShutdown()) {
|
|
||||||
return Status.UNAVAILABLE.withDescription("transport is shutdown");
|
|
||||||
} else {
|
|
||||||
serverTransportListener.streamCreated(stream, methodName, headers);
|
|
||||||
return Status.OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@GuardedBy("this")
|
|
||||||
void notifyShutdown(Status status) {
|
|
||||||
// Nothing to do.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@GuardedBy("this")
|
|
||||||
void notifyTerminated() {
|
|
||||||
if (serverTransportListener != null) {
|
|
||||||
serverTransportListener.transportTerminated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void shutdown() {
|
|
||||||
shutdownInternal(Status.OK, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void shutdownNow(Status reason) {
|
|
||||||
shutdownInternal(reason, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
@GuardedBy("this")
|
|
||||||
protected Inbound<?> createInbound(int callId) {
|
|
||||||
return new Inbound.ServerInbound(this, attributes, callId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InternalLogId buildLogId(Attributes attributes) {
|
|
||||||
return InternalLogId.allocate(
|
|
||||||
BinderServerTransport.class, "from " + attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkTransition(TransportState current, TransportState next) {
|
private static void checkTransition(TransportState current, TransportState next) {
|
||||||
switch (next) {
|
switch (next) {
|
||||||
case SETUP:
|
case SETUP:
|
||||||
|
|
|
@ -610,10 +610,9 @@ abstract class Inbound<L extends StreamListener> implements StreamListener.Messa
|
||||||
// Server-side inbound transactions.
|
// Server-side inbound transactions.
|
||||||
static final class ServerInbound extends Inbound<ServerStreamListener> {
|
static final class ServerInbound extends Inbound<ServerStreamListener> {
|
||||||
|
|
||||||
private final BinderTransport.BinderServerTransport serverTransport;
|
private final BinderServerTransport serverTransport;
|
||||||
|
|
||||||
ServerInbound(
|
ServerInbound(BinderServerTransport transport, Attributes attributes, int callId) {
|
||||||
BinderTransport.BinderServerTransport transport, Attributes attributes, int callId) {
|
|
||||||
super(transport, attributes, callId);
|
super(transport, attributes, callId);
|
||||||
this.serverTransport = transport;
|
this.serverTransport = transport;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,299 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static io.grpc.binder.internal.SystemApis.createContextAsUser;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import io.grpc.Attributes;
|
||||||
|
import io.grpc.EquivalentAddressGroup;
|
||||||
|
import io.grpc.NameResolver;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.StatusException;
|
||||||
|
import io.grpc.StatusOr;
|
||||||
|
import io.grpc.SynchronizationContext;
|
||||||
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import io.grpc.binder.ApiConstants;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link NameResolver} that resolves Android-standard "intent:" target URIs to the list of {@link
|
||||||
|
* AndroidComponentAddress} that match it by manifest intent filter.
|
||||||
|
*/
|
||||||
|
final class IntentNameResolver extends NameResolver {
|
||||||
|
private final Intent targetIntent; // Never mutated.
|
||||||
|
@Nullable private final UserHandle targetUser; // null means same user that hosts this process.
|
||||||
|
private final Context targetUserContext;
|
||||||
|
private final Executor offloadExecutor;
|
||||||
|
private final Executor sequentialExecutor;
|
||||||
|
private final SynchronizationContext syncContext;
|
||||||
|
private final ServiceConfigParser serviceConfigParser;
|
||||||
|
|
||||||
|
// Accessed only on `sequentialExecutor`
|
||||||
|
@Nullable private PackageChangeReceiver receiver; // != null when registered
|
||||||
|
|
||||||
|
// Accessed only on 'syncContext'.
|
||||||
|
private boolean shutdown;
|
||||||
|
private boolean queryNeeded;
|
||||||
|
@Nullable private Listener2 listener; // != null after start().
|
||||||
|
@Nullable private ListenableFuture<ResolutionResult> queryResultFuture; // != null when querying.
|
||||||
|
|
||||||
|
@EquivalentAddressGroup.Attr
|
||||||
|
private static final Attributes CONSTANT_EAG_ATTRS =
|
||||||
|
Attributes.newBuilder()
|
||||||
|
// Servers discovered in PackageManager are especially untrusted. After all, any app can
|
||||||
|
// declare any intent filter it wants! Require pre-authorization so that unauthorized apps
|
||||||
|
// don't even get a chance to run onCreate()/onBind().
|
||||||
|
.set(ApiConstants.PRE_AUTH_SERVER_OVERRIDE, true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
IntentNameResolver(Intent targetIntent, Args args) {
|
||||||
|
this.targetIntent = targetIntent;
|
||||||
|
this.targetUser = args.getArg(ApiConstants.TARGET_ANDROID_USER);
|
||||||
|
Context context =
|
||||||
|
checkNotNull(args.getArg(ApiConstants.SOURCE_ANDROID_CONTEXT), "SOURCE_ANDROID_CONTEXT")
|
||||||
|
.getApplicationContext();
|
||||||
|
this.targetUserContext =
|
||||||
|
targetUser != null ? createContextForTargetUserOrThrow(context, targetUser) : context;
|
||||||
|
// This Executor is nominally optional but all grpc-java Channels provide it since 1.25.
|
||||||
|
this.offloadExecutor =
|
||||||
|
checkNotNull(args.getOffloadExecutor(), "NameResolver.Args.getOffloadExecutor()");
|
||||||
|
// Ensures start()'s work runs before resolve()'s' work, and both run before shutdown()'s.
|
||||||
|
this.sequentialExecutor = MoreExecutors.newSequentialExecutor(offloadExecutor);
|
||||||
|
this.syncContext = args.getSynchronizationContext();
|
||||||
|
this.serviceConfigParser = args.getServiceConfigParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Context createContextForTargetUserOrThrow(Context context, UserHandle targetUser) {
|
||||||
|
try {
|
||||||
|
return createContextAsUser(context, targetUser, /* flags= */ 0); // @SystemApi since R.
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R and @SystemApi visibility");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Listener2 listener) {
|
||||||
|
checkState(this.listener == null, "Already started!");
|
||||||
|
checkState(!shutdown, "Resolver is shutdown");
|
||||||
|
this.listener = checkNotNull(listener);
|
||||||
|
sequentialExecutor.execute(this::registerReceiver);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
checkState(listener != null, "Not started!");
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolve() {
|
||||||
|
syncContext.throwIfNotInThisSynchronizationContext();
|
||||||
|
|
||||||
|
if (shutdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't block here in 'syncContext' so we offload PackageManager queries to an Executor.
|
||||||
|
// But offloading complicates things a bit because other calls can arrive while we wait for the
|
||||||
|
// results. We keep 'listener' up-to-date with the latest state in PackageManager by doing:
|
||||||
|
// 1. Only one query-and-report-to-listener operation at a time.
|
||||||
|
// 2. At least one query-and-report-to-listener AFTER every PackageManager state change.
|
||||||
|
if (queryResultFuture == null) {
|
||||||
|
queryResultFuture = Futures.submit(this::queryPackageManager, sequentialExecutor);
|
||||||
|
queryResultFuture.addListener(this::onQueryComplete, syncContext);
|
||||||
|
} else {
|
||||||
|
// There's already a query in-flight but (2) says we need at least one more. Our sequential
|
||||||
|
// Executor would be enough to ensure (1) but we also don't want a backlog of work to build up
|
||||||
|
// if things change rapidly. Just make a note to start a new query when this one finishes.
|
||||||
|
queryNeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onQueryComplete() {
|
||||||
|
syncContext.throwIfNotInThisSynchronizationContext();
|
||||||
|
checkState(queryResultFuture != null);
|
||||||
|
checkState(queryResultFuture.isDone());
|
||||||
|
|
||||||
|
// Capture non-final `listener` here while we're on 'syncContext'.
|
||||||
|
Listener2 listener = checkNotNull(this.listener);
|
||||||
|
Futures.addCallback(
|
||||||
|
queryResultFuture, // Already isDone() so this execute()s immediately.
|
||||||
|
new FutureCallback<ResolutionResult>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(ResolutionResult result) {
|
||||||
|
listener.onResult2(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
listener.onResult2(
|
||||||
|
ResolutionResult.newBuilder()
|
||||||
|
.setAddressesOrError(StatusOr.fromStatus(Status.fromThrowable(t)))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
syncContext); // Already on 'syncContext' but addCallback() is faster than try/get/catch.
|
||||||
|
queryResultFuture = null;
|
||||||
|
|
||||||
|
if (queryNeeded) {
|
||||||
|
// One or more resolve() requests arrived while we were working on the last one. Just one
|
||||||
|
// follow-on query can subsume all of them.
|
||||||
|
queryNeeded = false;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceAuthority() {
|
||||||
|
return "localhost";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
syncContext.throwIfNotInThisSynchronizationContext();
|
||||||
|
if (!shutdown) {
|
||||||
|
shutdown = true;
|
||||||
|
sequentialExecutor.execute(this::maybeUnregisterReceiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResolutionResult queryPackageManager() throws StatusException {
|
||||||
|
List<ResolveInfo> queryResults = queryIntentServices(targetIntent);
|
||||||
|
|
||||||
|
// Avoid a spurious UnsafeIntentLaunchViolation later. Since S, Android's StrictMode is very
|
||||||
|
// conservative, marking any Intent parsed from a string as suspicious and complaining when you
|
||||||
|
// bind to it. But all this is pointless with grpc-binder, which already goes even further by
|
||||||
|
// not trusting addresses at all! Instead, we rely on SecurityPolicy, which won't allow a
|
||||||
|
// connection to an unauthorized server UID no matter how you got there.
|
||||||
|
Intent prototypeBindIntent = sanitize(targetIntent);
|
||||||
|
|
||||||
|
// Model each matching android.app.Service as an EAG (server) with a single address.
|
||||||
|
List<EquivalentAddressGroup> addresses = new ArrayList<>();
|
||||||
|
for (ResolveInfo resolveInfo : queryResults) {
|
||||||
|
prototypeBindIntent.setComponent(
|
||||||
|
new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name));
|
||||||
|
addresses.add(
|
||||||
|
new EquivalentAddressGroup(
|
||||||
|
AndroidComponentAddress.newBuilder()
|
||||||
|
.setBindIntent(prototypeBindIntent) // Makes a copy.
|
||||||
|
.setTargetUser(targetUser)
|
||||||
|
.build(),
|
||||||
|
CONSTANT_EAG_ATTRS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResolutionResult.newBuilder()
|
||||||
|
.setAddressesOrError(StatusOr.fromValue(addresses))
|
||||||
|
// Empty service config means we get the default 'pick_first' load balancing policy.
|
||||||
|
.setServiceConfig(serviceConfigParser.parseServiceConfig(ImmutableMap.of()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ResolveInfo> queryIntentServices(Intent intent) throws StatusException {
|
||||||
|
int flags = 0;
|
||||||
|
if (Build.VERSION.SDK_INT >= 29) {
|
||||||
|
// Don't match direct-boot-unaware Services that can't presently be created. We'll query again
|
||||||
|
// after the user is unlocked. The MATCH_DIRECT_BOOT_AUTO behavior is actually the default but
|
||||||
|
// being explicit here avoids an android.os.strictmode.ImplicitDirectBootViolation.
|
||||||
|
flags |= PackageManager.MATCH_DIRECT_BOOT_AUTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ResolveInfo> intentServices =
|
||||||
|
targetUserContext.getPackageManager().queryIntentServices(intent, flags);
|
||||||
|
if (intentServices == null || intentServices.isEmpty()) {
|
||||||
|
// Must be the same as when ServiceBinding's call to bindService() returns false.
|
||||||
|
throw Status.UNIMPLEMENTED
|
||||||
|
.withDescription("Service not found for intent " + intent)
|
||||||
|
.asException();
|
||||||
|
}
|
||||||
|
return intentServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new Intent with the same action, data and categories as 'input'.
|
||||||
|
private static Intent sanitize(Intent input) {
|
||||||
|
Intent output = new Intent();
|
||||||
|
output.setAction(input.getAction());
|
||||||
|
output.setData(input.getData());
|
||||||
|
|
||||||
|
Set<String> categories = input.getCategories();
|
||||||
|
if (categories != null) {
|
||||||
|
for (String category : categories) {
|
||||||
|
output.addCategory(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't bother copying extras and flags since AndroidComponentAddress (rightly) ignores them.
|
||||||
|
// Don't bother copying package or ComponentName either, since we're about to set that.
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PackageChangeReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
// Get off the main thread and into the correct SynchronizationContext.
|
||||||
|
syncContext.executeLater(IntentNameResolver.this::resolve);
|
||||||
|
offloadExecutor.execute(syncContext::drain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnprotectedReceiver") // All of these are protected system broadcasts.
|
||||||
|
private void registerReceiver() {
|
||||||
|
checkState(receiver == null, "Already registered!");
|
||||||
|
receiver = new PackageChangeReceiver();
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addDataScheme("package");
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||||
|
|
||||||
|
targetUserContext.registerReceiver(receiver, filter);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 24) {
|
||||||
|
// Clients running in direct boot mode must refresh() when the user is unlocked because
|
||||||
|
// that's when `directBootAware=false` services become visible in queryIntentServices()
|
||||||
|
// results. ACTION_BOOT_COMPLETED would work too but it's delivered with lower priority.
|
||||||
|
targetUserContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeUnregisterReceiver() {
|
||||||
|
if (receiver != null) { // NameResolver API contract appears to allow shutdown without start().
|
||||||
|
targetUserContext.unregisterReceiver(receiver);
|
||||||
|
receiver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.grpc.binder.internal;
|
||||||
|
|
||||||
|
import static android.content.Intent.URI_INTENT_SCHEME;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import io.grpc.NameResolver;
|
||||||
|
import io.grpc.NameResolver.Args;
|
||||||
|
import io.grpc.NameResolverProvider;
|
||||||
|
import io.grpc.binder.AndroidComponentAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link NameResolverProvider} that handles Android-standard "intent:" target URIs, resolving
|
||||||
|
* them to the list of {@link AndroidComponentAddress} that match by manifest intent filter.
|
||||||
|
*/
|
||||||
|
public final class IntentNameResolverProvider extends NameResolverProvider {
|
||||||
|
|
||||||
|
static final String ANDROID_INTENT_SCHEME = "intent";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultScheme() {
|
||||||
|
return ANDROID_INTENT_SCHEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public NameResolver newNameResolver(URI targetUri, final Args args) {
|
||||||
|
if (Objects.equals(targetUri.getScheme(), ANDROID_INTENT_SCHEME)) {
|
||||||
|
return new IntentNameResolver(parseUriArg(targetUri), args);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int priority() {
|
||||||
|
return 3; // Lower than DNS so we don't accidentally become the default scheme for a registry.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableSet<Class<? extends SocketAddress>> getProducedSocketAddressTypes() {
|
||||||
|
return ImmutableSet.of(AndroidComponentAddress.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent parseUriArg(URI targetUri) {
|
||||||
|
try {
|
||||||
|
return Intent.parseUri(targetUri.toString(), URI_INTENT_SCHEME);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -193,18 +193,27 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
||||||
bindResult = context.bindService(bindIntent, conn, flags);
|
bindResult = context.bindService(bindIntent, conn, flags);
|
||||||
break;
|
break;
|
||||||
case BIND_SERVICE_AS_USER:
|
case BIND_SERVICE_AS_USER:
|
||||||
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
|
||||||
|
} else {
|
||||||
|
return Status.INTERNAL.withDescription("Cross user Channel requires Android R+");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DEVICE_POLICY_BIND_SEVICE_ADMIN:
|
case DEVICE_POLICY_BIND_SEVICE_ADMIN:
|
||||||
DevicePolicyManager devicePolicyManager =
|
DevicePolicyManager devicePolicyManager =
|
||||||
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||||
bindResult =
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
bindResult =
|
||||||
channelCredentials.getDevicePolicyAdminComponentName(),
|
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
||||||
bindIntent,
|
channelCredentials.getDevicePolicyAdminComponentName(),
|
||||||
conn,
|
bindIntent,
|
||||||
flags,
|
conn,
|
||||||
targetUserHandle);
|
flags,
|
||||||
|
targetUserHandle);
|
||||||
|
} else {
|
||||||
|
return Status.INTERNAL.withDescription(
|
||||||
|
"Device policy admin binding requires Android R+");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!bindResult) {
|
if (!bindResult) {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,12 +56,12 @@ public final class BinderServerTransportTest {
|
||||||
|
|
||||||
@Mock IBinder mockBinder;
|
@Mock IBinder mockBinder;
|
||||||
|
|
||||||
BinderTransport.BinderServerTransport transport;
|
BinderServerTransport transport;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
transport =
|
transport =
|
||||||
new BinderTransport.BinderServerTransport(
|
new BinderServerTransport(
|
||||||
new FixedObjectPool<>(executorService),
|
new FixedObjectPool<>(executorService),
|
||||||
Attributes.EMPTY,
|
Attributes.EMPTY,
|
||||||
ImmutableList.of(),
|
ImmutableList.of(),
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
|
@ -327,6 +328,50 @@ public final class ServiceBindingTest {
|
||||||
assertThat(statusException.getStatus().getDescription()).contains("12345");
|
assertThat(statusException.getStatus().getDescription()).contains("12345");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 30)
|
||||||
|
public void testBindService_doesNotThrowInternalErrorWhenSdkAtLeastR() {
|
||||||
|
UserHandle userHandle = generateUserHandle(/* userId= */ 12345);
|
||||||
|
binding = newBuilder().setTargetUserHandle(userHandle).build();
|
||||||
|
binding.bind();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
assertThat(Build.VERSION.SDK_INT).isEqualTo(Build.VERSION_CODES.R);
|
||||||
|
assertThat(observer.unboundReason).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 28)
|
||||||
|
public void testBindServiceAsUser_returnsErrorWhenSdkBelowR() {
|
||||||
|
UserHandle userHandle = generateUserHandle(/* userId= */ 12345);
|
||||||
|
binding = newBuilder().setTargetUserHandle(userHandle).build();
|
||||||
|
binding.bind();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.INTERNAL);
|
||||||
|
assertThat(observer.unboundReason.getDescription())
|
||||||
|
.isEqualTo("Cross user Channel requires Android R+");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 28)
|
||||||
|
public void testDevicePolicyBlind_returnsErrorWhenSdkBelowR() {
|
||||||
|
String deviceAdminClassName = "DevicePolicyAdmin";
|
||||||
|
ComponentName adminComponent = new ComponentName(appContext, deviceAdminClassName);
|
||||||
|
allowBindDeviceAdminForUser(appContext, adminComponent, 10);
|
||||||
|
binding =
|
||||||
|
newBuilder()
|
||||||
|
.setTargetUserHandle(UserHandle.getUserHandleForUid(10))
|
||||||
|
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
|
||||||
|
.build();
|
||||||
|
binding.bind();
|
||||||
|
shadowOf(getMainLooper()).idle();
|
||||||
|
|
||||||
|
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.INTERNAL);
|
||||||
|
assertThat(observer.unboundReason.getDescription())
|
||||||
|
.isEqualTo("Device policy admin binding requires Android R+");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = 30)
|
@Config(sdk = 30)
|
||||||
public void testBindWithDeviceAdmin() throws Exception {
|
public void testBindWithDeviceAdmin() throws Exception {
|
||||||
|
|
|
@ -24,8 +24,8 @@ import io.grpc.internal.TestUtils.NoopChannelLogger;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helps unit tests create {@link BinderTransport.BinderClientTransport} instances without having to
|
* Helps unit tests create {@link BinderClientTransport} instances without having to mention
|
||||||
* mention irrelevant details (go/tott/719).
|
* irrelevant details (go/tott/719).
|
||||||
*/
|
*/
|
||||||
public class BinderClientTransportBuilder {
|
public class BinderClientTransportBuilder {
|
||||||
private BinderClientTransportFactory factory;
|
private BinderClientTransportFactory factory;
|
||||||
|
@ -54,7 +54,7 @@ public class BinderClientTransportBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinderTransport.BinderClientTransport build() {
|
public BinderClientTransport build() {
|
||||||
return factory.newClientTransport(
|
return factory.newClientTransport(
|
||||||
checkNotNull(serverAddress), checkNotNull(options), checkNotNull(channelLogger));
|
checkNotNull(serverAddress), checkNotNull(options), checkNotNull(channelLogger));
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ subprojects {
|
||||||
apply plugin: "net.ltgt.errorprone"
|
apply plugin: "net.ltgt.errorprone"
|
||||||
|
|
||||||
group = "io.grpc"
|
group = "io.grpc"
|
||||||
version = "1.75.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
version = "1.76.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { // The google mirror is less flaky than mavenCentral()
|
maven { // The google mirror is less flaky than mavenCentral()
|
||||||
|
|
|
@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
@javax.annotation.Generated(
|
||||||
value = "by gRPC proto compiler (version 1.75.0-SNAPSHOT)",
|
value = "by gRPC proto compiler (version 1.76.0-SNAPSHOT)",
|
||||||
comments = "Source: grpc/testing/compiler/test.proto")
|
comments = "Source: grpc/testing/compiler/test.proto")
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
@java.lang.Deprecated
|
@java.lang.Deprecated
|
||||||
|
|
|
@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@javax.annotation.Generated(
|
@javax.annotation.Generated(
|
||||||
value = "by gRPC proto compiler (version 1.75.0-SNAPSHOT)",
|
value = "by gRPC proto compiler (version 1.76.0-SNAPSHOT)",
|
||||||
comments = "Source: grpc/testing/compiler/test.proto")
|
comments = "Source: grpc/testing/compiler/test.proto")
|
||||||
@io.grpc.stub.annotations.GrpcGenerated
|
@io.grpc.stub.annotations.GrpcGenerated
|
||||||
public final class TestServiceGrpc {
|
public final class TestServiceGrpc {
|
||||||
|
|
|
@ -219,7 +219,7 @@ public final class GrpcUtil {
|
||||||
|
|
||||||
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
|
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
|
||||||
|
|
||||||
public static final String IMPLEMENTATION_VERSION = "1.75.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
|
public static final String IMPLEMENTATION_VERSION = "1.76.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default timeout in nanos for a keepalive ping request.
|
* The default timeout in nanos for a keepalive ping request.
|
||||||
|
|
|
@ -14,7 +14,7 @@ android {
|
||||||
namespace = 'io.grpc.cronet'
|
namespace = 'io.grpc.cronet'
|
||||||
compileSdkVersion 33
|
compileSdkVersion 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 22
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
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.76.0-SNAPSHOT") # CURRENT_GRPC_VERSION
|
||||||
bazel_dep(name = "grpc-java", repo_name = "io_grpc_grpc_java", version = "1.75.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 = "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 = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
|
||||||
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
||||||
|
|
|
@ -34,7 +34,7 @@ android {
|
||||||
protobuf {
|
protobuf {
|
||||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||||
plugins {
|
plugins {
|
||||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
|
@ -54,12 +54,12 @@ dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
|
|
||||||
// You need to build grpc-java to obtain these libraries below.
|
// You need to build grpc-java to obtain these libraries below.
|
||||||
implementation 'io.grpc:grpc-okhttp:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
implementation 'io.grpc:grpc-stub:1.75.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'
|
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'com.google.truth:truth:1.1.5'
|
testImplementation 'com.google.truth:truth:1.1.5'
|
||||||
testImplementation 'io.grpc:grpc-testing:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
testImplementation 'io.grpc:grpc-testing:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ android {
|
||||||
protobuf {
|
protobuf {
|
||||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||||
plugins {
|
plugins {
|
||||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
|
@ -52,8 +52,8 @@ dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
|
|
||||||
// You need to build grpc-java to obtain these libraries below.
|
// You need to build grpc-java to obtain these libraries below.
|
||||||
implementation 'io.grpc:grpc-okhttp:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
implementation 'io.grpc:grpc-stub:1.75.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'
|
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ android {
|
||||||
protobuf {
|
protobuf {
|
||||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||||
plugins {
|
plugins {
|
||||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
|
@ -52,8 +52,8 @@ dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
|
|
||||||
// You need to build grpc-java to obtain these libraries below.
|
// You need to build grpc-java to obtain these libraries below.
|
||||||
implementation 'io.grpc:grpc-okhttp:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
implementation 'io.grpc:grpc-stub:1.75.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'
|
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ android {
|
||||||
protobuf {
|
protobuf {
|
||||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||||
plugins {
|
plugins {
|
||||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
|
@ -53,8 +53,8 @@ dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
|
|
||||||
// You need to build grpc-java to obtain these libraries below.
|
// You need to build grpc-java to obtain these libraries below.
|
||||||
implementation 'io.grpc:grpc-okhttp:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
implementation 'io.grpc:grpc-stub:1.75.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'
|
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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protobufVersion = '3.25.5'
|
def protobufVersion = '3.25.8'
|
||||||
def protocVersion = protobufVersion
|
def protocVersion = protobufVersion
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -21,8 +21,8 @@ java {
|
||||||
|
|
||||||
// Feel free to delete the comment at the next line. It is just for safely
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// grpc-alts transitively depends on grpc-netty-shaded, grpc-protobuf, and grpc-stub
|
// 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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protobufVersion = '3.25.5'
|
def protobufVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
for safely updating the version in our release process. -->
|
for safely updating the version in our release process. -->
|
||||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<name>example-debug</name>
|
<name>example-debug</name>
|
||||||
<url>https://github.com/grpc/grpc-java</url>
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<protoc.version>3.25.5</protoc.version>
|
<protoc.version>3.25.8</protoc.version>
|
||||||
<!-- required for jdk9 -->
|
<!-- required for jdk9 -->
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protobufVersion = '3.25.5'
|
def protobufVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
for safely updating the version in our release process. -->
|
for safely updating the version in our release process. -->
|
||||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<name>example-dualstack</name>
|
<name>example-dualstack</name>
|
||||||
<url>https://github.com/grpc/grpc-java</url>
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<protoc.version>3.25.5</protoc.version>
|
<protoc.version>3.25.8</protoc.version>
|
||||||
<!-- required for jdk9 -->
|
<!-- required for jdk9 -->
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protobufVersion = '3.25.5'
|
def protobufVersion = '3.25.8'
|
||||||
def protocVersion = protobufVersion
|
def protocVersion = protobufVersion
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
for safely updating the version in our release process. -->
|
for safely updating the version in our release process. -->
|
||||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<name>example-gauth</name>
|
<name>example-gauth</name>
|
||||||
<url>https://github.com/grpc/grpc-java</url>
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<protobuf.version>3.25.5</protobuf.version>
|
<protobuf.version>3.25.8</protobuf.version>
|
||||||
<!-- required for jdk9 -->
|
<!-- required for jdk9 -->
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
def openTelemetryVersion = '1.40.0'
|
def openTelemetryVersion = '1.52.0'
|
||||||
def openTelemetryPrometheusVersion = '1.40.0-alpha'
|
def openTelemetryPrometheusVersion = '1.52.0-alpha'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protobufVersion = '3.25.5'
|
def protobufVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
for safely updating the version in our release process. -->
|
for safely updating the version in our release process. -->
|
||||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<name>example-hostname</name>
|
<name>example-hostname</name>
|
||||||
<url>https://github.com/grpc/grpc-java</url>
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<protoc.version>3.25.5</protoc.version>
|
<protoc.version>3.25.8</protoc.version>
|
||||||
<!-- required for jdk9 -->
|
<!-- required for jdk9 -->
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protobufVersion = '3.25.5'
|
def protobufVersion = '3.25.8'
|
||||||
def protocVersion = protobufVersion
|
def protocVersion = protobufVersion
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -7,15 +7,15 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
for safely updating the version in our release process. -->
|
for safely updating the version in our release process. -->
|
||||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<name>example-jwt-auth</name>
|
<name>example-jwt-auth</name>
|
||||||
<url>https://github.com/grpc/grpc-java</url>
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<protobuf.version>3.25.5</protobuf.version>
|
<protobuf.version>3.25.8</protobuf.version>
|
||||||
<protoc.version>3.25.5</protoc.version>
|
<protoc.version>3.25.8</protoc.version>
|
||||||
<!-- required for jdk9 -->
|
<!-- required for jdk9 -->
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protobufVersion = '3.25.5'
|
def protobufVersion = '3.25.8'
|
||||||
def protocVersion = protobufVersion
|
def protocVersion = protobufVersion
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -7,15 +7,15 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
for safely updating the version in our release process. -->
|
for safely updating the version in our release process. -->
|
||||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<name>example-oauth</name>
|
<name>example-oauth</name>
|
||||||
<url>https://github.com/grpc/grpc-java</url>
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<protobuf.version>3.25.5</protobuf.version>
|
<protobuf.version>3.25.8</protobuf.version>
|
||||||
<protoc.version>3.25.5</protoc.version>
|
<protoc.version>3.25.8</protoc.version>
|
||||||
<!-- required for jdk9 -->
|
<!-- required for jdk9 -->
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
def openTelemetryVersion = '1.40.0'
|
def openTelemetryVersion = '1.52.0'
|
||||||
def openTelemetryPrometheusVersion = '1.40.0-alpha'
|
def openTelemetryPrometheusVersion = '1.52.0-alpha'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
|
|
@ -16,8 +16,8 @@ java {
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
|
|
@ -16,8 +16,8 @@ java {
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
|
|
@ -15,8 +15,8 @@ java {
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}",
|
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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
for safely updating the version in our release process. -->
|
for safely updating the version in our release process. -->
|
||||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<name>example-tls</name>
|
<name>example-tls</name>
|
||||||
<url>https://github.com/grpc/grpc-java</url>
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<protoc.version>3.25.5</protoc.version>
|
<protoc.version>3.25.8</protoc.version>
|
||||||
<!-- required for jdk9 -->
|
<!-- required for jdk9 -->
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<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
|
// Feel free to delete the comment at the next line. It is just for safely
|
||||||
// updating the version in our release process.
|
// updating the version in our release process.
|
||||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
def protocVersion = '3.25.5'
|
def protocVersion = '3.25.8'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
|
|
@ -6,15 +6,15 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||||
for safely updating the version in our release process. -->
|
for safely updating the version in our release process. -->
|
||||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<name>examples</name>
|
<name>examples</name>
|
||||||
<url>https://github.com/grpc/grpc-java</url>
|
<url>https://github.com/grpc/grpc-java</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||||
<protobuf.version>3.25.5</protobuf.version>
|
<protobuf.version>3.25.8</protobuf.version>
|
||||||
<protoc.version>3.25.5</protoc.version>
|
<protoc.version>3.25.8</protoc.version>
|
||||||
<!-- required for JDK 8 -->
|
<!-- required for JDK 8 -->
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
[versions]
|
[versions]
|
||||||
netty = '4.1.110.Final'
|
netty = '4.1.124.Final'
|
||||||
# Keep the following references of tcnative version in sync whenever it's updated:
|
# Keep the following references of tcnative version in sync whenever it's updated:
|
||||||
# SECURITY.md
|
# SECURITY.md
|
||||||
nettytcnative = '2.0.70.Final'
|
nettytcnative = '2.0.72.Final'
|
||||||
opencensus = "0.31.1"
|
opencensus = "0.31.1"
|
||||||
# Not upgrading to 4.x as it is not yet ABI compatible.
|
# Not upgrading to 4.x as it is not yet ABI compatible.
|
||||||
# https://github.com/protocolbuffers/protobuf/issues/17247
|
# https://github.com/protocolbuffers/protobuf/issues/17247
|
||||||
protobuf = "3.25.5"
|
protobuf = "3.25.8"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-annotations = "com.google.android:annotations:4.1.1.4"
|
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"
|
androidx-annotation = "androidx.annotation:annotation:1.9.0"
|
||||||
# 1.15.0 requires libraries and applications that depend on it to compile against
|
# 1.15.0 requires libraries and applications that depend on it to compile against
|
||||||
# version 35 or later of the Android APIs.
|
# version 35 or later of the Android APIs.
|
||||||
androidx-core = "androidx.core:core:1.13.1"
|
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-common = "androidx.lifecycle:lifecycle-common:2.8.7"
|
||||||
androidx-lifecycle-service = "androidx.lifecycle:lifecycle-service:2.8.7"
|
androidx-lifecycle-service = "androidx.lifecycle:lifecycle-service:2.8.7"
|
||||||
androidx-test-core = "androidx.test:core:1.6.1"
|
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"
|
errorprone-annotations = "com.google.errorprone:error_prone_annotations:2.30.0"
|
||||||
# error-prone 2.32.0+ require Java 17+
|
# error-prone 2.32.0+ require Java 17+
|
||||||
errorprone-core = "com.google.errorprone:error_prone_core:2.31.0"
|
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
|
# google-auth-library 1.25.0+ requires error_prone_annotations 2.31.0+, which
|
||||||
# breaks the Android build
|
# breaks the Android build
|
||||||
google-auth-credentials = "com.google.auth:google-auth-library-credentials:1.24.1"
|
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"
|
google-auth-oauth2Http = "com.google.auth:google-auth-library-oauth2-http:1.24.1"
|
||||||
# Release notes: https://cloud.google.com/logging/docs/release-notes
|
# 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
|
# 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"
|
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)
|
# 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"
|
javax-servlet-api = "javax.servlet:javax.servlet-api:4.0.1"
|
||||||
# 12.0.0+ require Java 17+
|
# 12.0.0+ require Java 17+
|
||||||
jetty-client = "org.eclipse.jetty:jetty-client:11.0.24"
|
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-http2-server10 = "org.eclipse.jetty.http2:http2-server:10.0.20"
|
||||||
jetty-servlet = "org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.16"
|
jetty-servlet = "org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.16"
|
||||||
jetty-servlet10 = "org.eclipse.jetty:jetty-servlet:10.0.20"
|
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-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-exporter-trace-stackdriver = { module = "io.opencensus:opencensus-exporter-trace-stackdriver", version.ref = "opencensus" }
|
||||||
opencensus-impl = { module = "io.opencensus:opencensus-impl", version.ref = "opencensus" }
|
opencensus-impl = { module = "io.opencensus:opencensus-impl", version.ref = "opencensus" }
|
||||||
opentelemetry-api = "io.opentelemetry:opentelemetry-api:1.46.0"
|
opentelemetry-api = "io.opentelemetry:opentelemetry-api:1.52.0"
|
||||||
opentelemetry-exporter-prometheus = "io.opentelemetry:opentelemetry-exporter-prometheus:1.46.0-alpha"
|
opentelemetry-exporter-prometheus = "io.opentelemetry:opentelemetry-exporter-prometheus:1.52.0-alpha"
|
||||||
opentelemetry-gcp-resources = "io.opentelemetry.contrib:opentelemetry-gcp-resources:1.43.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.46.0"
|
opentelemetry-sdk-extension-autoconfigure = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.52.0"
|
||||||
opentelemetry-sdk-testing = "io.opentelemetry:opentelemetry-sdk-testing:1.46.0"
|
opentelemetry-sdk-testing = "io.opentelemetry:opentelemetry-sdk-testing:1.52.0"
|
||||||
perfmark-api = "io.perfmark:perfmark-api:0.27.0"
|
perfmark-api = "io.perfmark:perfmark-api:0.27.0"
|
||||||
protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
|
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-java-util = { module = "com.google.protobuf:protobuf-java-util", version.ref = "protobuf" }
|
||||||
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
|
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
|
||||||
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
|
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
|
||||||
re2j = "com.google.re2j:re2j:1.8"
|
re2j = "com.google.re2j:re2j:1.8"
|
||||||
robolectric = "org.robolectric:robolectric:4.14.1"
|
robolectric = "org.robolectric:robolectric:4.15.1"
|
||||||
s2a-proto = "com.google.s2a.proto.v2:s2a-proto:0.1.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-android = "net.sf.androidscents.signature:android-api-level-21:5.0.1_r2"
|
||||||
signature-java = "org.codehaus.mojo.signature:java18:1.0"
|
signature-java = "org.codehaus.mojo.signature:java18:1.0"
|
||||||
# 11.0.0+ require Java 17+
|
# 11.0.0+ require Java 17+
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<lint>
|
||||||
|
<!--
|
||||||
|
Suppress temporarily due to AAPT2 failures with SDK 35/36 on AGP 7.x.
|
||||||
|
Remove after AGP upgrade.
|
||||||
|
-->
|
||||||
|
<issue id="OldTargetApi" severity="ignore" />
|
||||||
|
</lint>
|
|
@ -84,6 +84,7 @@ public class GrpcSslContexts {
|
||||||
private static final String SUN_PROVIDER_NAME = "SunJSSE";
|
private static final String SUN_PROVIDER_NAME = "SunJSSE";
|
||||||
private static final String IBM_PROVIDER_NAME = "IBMJSSE2";
|
private static final String IBM_PROVIDER_NAME = "IBMJSSE2";
|
||||||
private static final String OPENJSSE_PROVIDER_NAME = "OpenJSSE";
|
private static final String OPENJSSE_PROVIDER_NAME = "OpenJSSE";
|
||||||
|
private static final String BCJSSE_PROVIDER_NAME = "BCJSSE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an SslContextBuilder with ciphers and APN appropriate for gRPC.
|
* Creates an SslContextBuilder with ciphers and APN appropriate for gRPC.
|
||||||
|
@ -199,7 +200,8 @@ public class GrpcSslContexts {
|
||||||
jdkProvider.getName() + " selected, but Java 9+ and Jetty NPN/ALPN unavailable");
|
jdkProvider.getName() + " selected, but Java 9+ and Jetty NPN/ALPN unavailable");
|
||||||
}
|
}
|
||||||
} else if (IBM_PROVIDER_NAME.equals(jdkProvider.getName())
|
} else if (IBM_PROVIDER_NAME.equals(jdkProvider.getName())
|
||||||
|| OPENJSSE_PROVIDER_NAME.equals(jdkProvider.getName())) {
|
|| OPENJSSE_PROVIDER_NAME.equals(jdkProvider.getName())
|
||||||
|
|| BCJSSE_PROVIDER_NAME.equals(jdkProvider.getName())) {
|
||||||
if (JettyTlsUtil.isJava9AlpnAvailable()) {
|
if (JettyTlsUtil.isJava9AlpnAvailable()) {
|
||||||
apc = ALPN;
|
apc = ALPN;
|
||||||
} else {
|
} else {
|
||||||
|
@ -255,7 +257,8 @@ public class GrpcSslContexts {
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
} else if (IBM_PROVIDER_NAME.equals(provider.getName())
|
} else if (IBM_PROVIDER_NAME.equals(provider.getName())
|
||||||
|| OPENJSSE_PROVIDER_NAME.equals(provider.getName())) {
|
|| OPENJSSE_PROVIDER_NAME.equals(provider.getName())
|
||||||
|
|| BCJSSE_PROVIDER_NAME.equals(provider.getName())) {
|
||||||
if (JettyTlsUtil.isJava9AlpnAvailable()) {
|
if (JettyTlsUtil.isJava9AlpnAvailable()) {
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelFutureListener;
|
import io.netty.channel.ChannelFutureListener;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.handler.codec.http2.DecoratingHttp2ConnectionEncoder;
|
||||||
import io.netty.handler.codec.http2.DecoratingHttp2FrameWriter;
|
import io.netty.handler.codec.http2.DecoratingHttp2FrameWriter;
|
||||||
import io.netty.handler.codec.http2.DefaultHttp2Connection;
|
import io.netty.handler.codec.http2.DefaultHttp2Connection;
|
||||||
import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
|
import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
|
||||||
|
@ -83,6 +84,7 @@ import io.netty.handler.codec.http2.Http2FrameWriter;
|
||||||
import io.netty.handler.codec.http2.Http2Headers;
|
import io.netty.handler.codec.http2.Http2Headers;
|
||||||
import io.netty.handler.codec.http2.Http2HeadersDecoder;
|
import io.netty.handler.codec.http2.Http2HeadersDecoder;
|
||||||
import io.netty.handler.codec.http2.Http2InboundFrameLogger;
|
import io.netty.handler.codec.http2.Http2InboundFrameLogger;
|
||||||
|
import io.netty.handler.codec.http2.Http2LifecycleManager;
|
||||||
import io.netty.handler.codec.http2.Http2OutboundFrameLogger;
|
import io.netty.handler.codec.http2.Http2OutboundFrameLogger;
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.codec.http2.Http2Stream;
|
import io.netty.handler.codec.http2.Http2Stream;
|
||||||
|
@ -125,13 +127,11 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
private final long keepAliveTimeoutInNanos;
|
private final long keepAliveTimeoutInNanos;
|
||||||
private final long maxConnectionAgeInNanos;
|
private final long maxConnectionAgeInNanos;
|
||||||
private final long maxConnectionAgeGraceInNanos;
|
private final long maxConnectionAgeGraceInNanos;
|
||||||
private final int maxRstCount;
|
private final RstStreamCounter rstStreamCounter;
|
||||||
private final long maxRstPeriodNanos;
|
|
||||||
private final List<? extends ServerStreamTracer.Factory> streamTracerFactories;
|
private final List<? extends ServerStreamTracer.Factory> streamTracerFactories;
|
||||||
private final TransportTracer transportTracer;
|
private final TransportTracer transportTracer;
|
||||||
private final KeepAliveEnforcer keepAliveEnforcer;
|
private final KeepAliveEnforcer keepAliveEnforcer;
|
||||||
private final Attributes eagAttributes;
|
private final Attributes eagAttributes;
|
||||||
private final Ticker ticker;
|
|
||||||
/** Incomplete attributes produced by negotiator. */
|
/** Incomplete attributes produced by negotiator. */
|
||||||
private Attributes negotiationAttributes;
|
private Attributes negotiationAttributes;
|
||||||
private InternalChannelz.Security securityInfo;
|
private InternalChannelz.Security securityInfo;
|
||||||
|
@ -149,8 +149,6 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
private ScheduledFuture<?> maxConnectionAgeMonitor;
|
private ScheduledFuture<?> maxConnectionAgeMonitor;
|
||||||
@CheckForNull
|
@CheckForNull
|
||||||
private GracefulShutdown gracefulShutdown;
|
private GracefulShutdown gracefulShutdown;
|
||||||
private int rstCount;
|
|
||||||
private long lastRstNanoTime;
|
|
||||||
|
|
||||||
static NettyServerHandler newHandler(
|
static NettyServerHandler newHandler(
|
||||||
ServerTransportListener transportListener,
|
ServerTransportListener transportListener,
|
||||||
|
@ -251,6 +249,12 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
final KeepAliveEnforcer keepAliveEnforcer = new KeepAliveEnforcer(
|
final KeepAliveEnforcer keepAliveEnforcer = new KeepAliveEnforcer(
|
||||||
permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos, TimeUnit.NANOSECONDS);
|
permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos, TimeUnit.NANOSECONDS);
|
||||||
|
|
||||||
|
if (ticker == null) {
|
||||||
|
ticker = Ticker.systemTicker();
|
||||||
|
}
|
||||||
|
|
||||||
|
RstStreamCounter rstStreamCounter
|
||||||
|
= new RstStreamCounter(maxRstCount, maxRstPeriodNanos, ticker);
|
||||||
// Create the local flow controller configured to auto-refill the connection window.
|
// Create the local flow controller configured to auto-refill the connection window.
|
||||||
connection.local().flowController(
|
connection.local().flowController(
|
||||||
new DefaultHttp2LocalFlowController(connection, DEFAULT_WINDOW_UPDATE_RATIO, true));
|
new DefaultHttp2LocalFlowController(connection, DEFAULT_WINDOW_UPDATE_RATIO, true));
|
||||||
|
@ -258,6 +262,7 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
Http2ConnectionEncoder encoder =
|
Http2ConnectionEncoder encoder =
|
||||||
new DefaultHttp2ConnectionEncoder(connection, frameWriter);
|
new DefaultHttp2ConnectionEncoder(connection, frameWriter);
|
||||||
encoder = new Http2ControlFrameLimitEncoder(encoder, 10000);
|
encoder = new Http2ControlFrameLimitEncoder(encoder, 10000);
|
||||||
|
encoder = new Http2RstCounterEncoder(encoder, rstStreamCounter);
|
||||||
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder,
|
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder,
|
||||||
frameReader);
|
frameReader);
|
||||||
|
|
||||||
|
@ -266,10 +271,6 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
settings.maxConcurrentStreams(maxStreams);
|
settings.maxConcurrentStreams(maxStreams);
|
||||||
settings.maxHeaderListSize(maxHeaderListSize);
|
settings.maxHeaderListSize(maxHeaderListSize);
|
||||||
|
|
||||||
if (ticker == null) {
|
|
||||||
ticker = Ticker.systemTicker();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NettyServerHandler(
|
return new NettyServerHandler(
|
||||||
channelUnused,
|
channelUnused,
|
||||||
connection,
|
connection,
|
||||||
|
@ -286,8 +287,7 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
|
maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
|
||||||
keepAliveEnforcer,
|
keepAliveEnforcer,
|
||||||
autoFlowControl,
|
autoFlowControl,
|
||||||
maxRstCount,
|
rstStreamCounter,
|
||||||
maxRstPeriodNanos,
|
|
||||||
eagAttributes, ticker);
|
eagAttributes, ticker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,8 +310,7 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
long maxConnectionAgeGraceInNanos,
|
long maxConnectionAgeGraceInNanos,
|
||||||
final KeepAliveEnforcer keepAliveEnforcer,
|
final KeepAliveEnforcer keepAliveEnforcer,
|
||||||
boolean autoFlowControl,
|
boolean autoFlowControl,
|
||||||
int maxRstCount,
|
RstStreamCounter rstStreamCounter,
|
||||||
long maxRstPeriodNanos,
|
|
||||||
Attributes eagAttributes,
|
Attributes eagAttributes,
|
||||||
Ticker ticker) {
|
Ticker ticker) {
|
||||||
super(
|
super(
|
||||||
|
@ -363,12 +362,9 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
|
this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
|
||||||
this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
|
this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
|
||||||
this.keepAliveEnforcer = checkNotNull(keepAliveEnforcer, "keepAliveEnforcer");
|
this.keepAliveEnforcer = checkNotNull(keepAliveEnforcer, "keepAliveEnforcer");
|
||||||
this.maxRstCount = maxRstCount;
|
this.rstStreamCounter = rstStreamCounter;
|
||||||
this.maxRstPeriodNanos = maxRstPeriodNanos;
|
|
||||||
this.eagAttributes = checkNotNull(eagAttributes, "eagAttributes");
|
this.eagAttributes = checkNotNull(eagAttributes, "eagAttributes");
|
||||||
this.ticker = checkNotNull(ticker, "ticker");
|
|
||||||
|
|
||||||
this.lastRstNanoTime = ticker.read();
|
|
||||||
streamKey = encoder.connection().newKey();
|
streamKey = encoder.connection().newKey();
|
||||||
this.transportListener = checkNotNull(transportListener, "transportListener");
|
this.transportListener = checkNotNull(transportListener, "transportListener");
|
||||||
this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
|
this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
|
||||||
|
@ -575,24 +571,9 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRstStreamRead(int streamId, long errorCode) throws Http2Exception {
|
private void onRstStreamRead(int streamId, long errorCode) throws Http2Exception {
|
||||||
if (maxRstCount > 0) {
|
Http2Exception tooManyRstStream = rstStreamCounter.countRstStream();
|
||||||
long now = ticker.read();
|
if (tooManyRstStream != null) {
|
||||||
if (now - lastRstNanoTime > maxRstPeriodNanos) {
|
throw tooManyRstStream;
|
||||||
lastRstNanoTime = now;
|
|
||||||
rstCount = 1;
|
|
||||||
} else {
|
|
||||||
rstCount++;
|
|
||||||
if (rstCount > maxRstCount) {
|
|
||||||
throw new Http2Exception(Http2Error.ENHANCE_YOUR_CALM, "too_many_rststreams") {
|
|
||||||
@SuppressWarnings("UnsynchronizedOverridesSynchronized") // No memory accesses
|
|
||||||
@Override
|
|
||||||
public Throwable fillInStackTrace() {
|
|
||||||
// Avoid the CPU cycles, since the resets may be a CPU consumption attack
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1180,6 +1161,81 @@ class NettyServerHandler extends AbstractNettyHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class Http2RstCounterEncoder extends DecoratingHttp2ConnectionEncoder {
|
||||||
|
private final RstStreamCounter rstStreamCounter;
|
||||||
|
private Http2LifecycleManager lifecycleManager;
|
||||||
|
|
||||||
|
Http2RstCounterEncoder(Http2ConnectionEncoder encoder, RstStreamCounter rstStreamCounter) {
|
||||||
|
super(encoder);
|
||||||
|
this.rstStreamCounter = rstStreamCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
|
||||||
|
this.lifecycleManager = lifecycleManager;
|
||||||
|
super.lifecycleManager(lifecycleManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture writeRstStream(
|
||||||
|
ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) {
|
||||||
|
ChannelFuture future = super.writeRstStream(ctx, streamId, errorCode, promise);
|
||||||
|
// We want to count "induced" RST_STREAM, where the server sent a reset because of a malformed
|
||||||
|
// frame.
|
||||||
|
boolean normalRst
|
||||||
|
= errorCode == Http2Error.NO_ERROR.code() || errorCode == Http2Error.CANCEL.code();
|
||||||
|
if (!normalRst) {
|
||||||
|
Http2Exception tooManyRstStream = rstStreamCounter.countRstStream();
|
||||||
|
if (tooManyRstStream != null) {
|
||||||
|
lifecycleManager.onError(ctx, true, tooManyRstStream);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RstStreamCounter {
|
||||||
|
private final int maxRstCount;
|
||||||
|
private final long maxRstPeriodNanos;
|
||||||
|
private final Ticker ticker;
|
||||||
|
private int rstCount;
|
||||||
|
private long lastRstNanoTime;
|
||||||
|
|
||||||
|
RstStreamCounter(int maxRstCount, long maxRstPeriodNanos, Ticker ticker) {
|
||||||
|
checkArgument(maxRstCount >= 0, "maxRstCount must be non-negative: %s", maxRstCount);
|
||||||
|
this.maxRstCount = maxRstCount;
|
||||||
|
this.maxRstPeriodNanos = maxRstPeriodNanos;
|
||||||
|
this.ticker = checkNotNull(ticker, "ticker");
|
||||||
|
this.lastRstNanoTime = ticker.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns non-{@code null} when the connection should be killed by the caller. */
|
||||||
|
private Http2Exception countRstStream() {
|
||||||
|
if (maxRstCount == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long now = ticker.read();
|
||||||
|
if (now - lastRstNanoTime > maxRstPeriodNanos) {
|
||||||
|
lastRstNanoTime = now;
|
||||||
|
rstCount = 1;
|
||||||
|
} else {
|
||||||
|
rstCount++;
|
||||||
|
if (rstCount > maxRstCount) {
|
||||||
|
return new Http2Exception(Http2Error.ENHANCE_YOUR_CALM, "too_many_rststreams") {
|
||||||
|
@SuppressWarnings("UnsynchronizedOverridesSynchronized") // No memory accesses
|
||||||
|
@Override
|
||||||
|
public Throwable fillInStackTrace() {
|
||||||
|
// Avoid the CPU cycles, since the resets may be a CPU consumption attack
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class ServerChannelLogger extends ChannelLogger {
|
private static class ServerChannelLogger extends ChannelLogger {
|
||||||
private static final Logger log = Logger.getLogger(ChannelLogger.class.getName());
|
private static final Logger log = Logger.getLogger(ChannelLogger.class.getName());
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ import static io.grpc.netty.Utils.HTTP_METHOD;
|
||||||
import static io.grpc.netty.Utils.STATUS_OK;
|
import static io.grpc.netty.Utils.STATUS_OK;
|
||||||
import static io.grpc.netty.Utils.TE_HEADER;
|
import static io.grpc.netty.Utils.TE_HEADER;
|
||||||
import static io.grpc.netty.Utils.TE_TRAILERS;
|
import static io.grpc.netty.Utils.TE_TRAILERS;
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -276,7 +275,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHand
|
||||||
public void createStreamShouldSucceed() throws Exception {
|
public void createStreamShouldSucceed() throws Exception {
|
||||||
createStream();
|
createStream();
|
||||||
verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(grpcHeaders), eq(0),
|
verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(grpcHeaders), eq(0),
|
||||||
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
|
eq(false), any(ChannelPromise.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1304,6 +1304,8 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase<NettyServerHand
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rapidReset(int burstSize) throws Exception {
|
private void rapidReset(int burstSize) throws Exception {
|
||||||
|
when(streamTracerFactory.newServerStreamTracer(anyString(), any(Metadata.class)))
|
||||||
|
.thenAnswer((args) -> new TestServerStreamTracer());
|
||||||
Http2Headers headers = new DefaultHttp2Headers()
|
Http2Headers headers = new DefaultHttp2Headers()
|
||||||
.method(HTTP_METHOD)
|
.method(HTTP_METHOD)
|
||||||
.set(CONTENT_TYPE_HEADER, new AsciiString("application/grpc", UTF_8))
|
.set(CONTENT_TYPE_HEADER, new AsciiString("application/grpc", UTF_8))
|
||||||
|
@ -1323,6 +1325,48 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase<NettyServerHand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maxRstCountSent_withinLimit_succeeds() throws Exception {
|
||||||
|
maxRstCount = 10;
|
||||||
|
maxRstPeriodNanos = TimeUnit.MILLISECONDS.toNanos(100);
|
||||||
|
manualSetUp();
|
||||||
|
madeYouReset(maxRstCount);
|
||||||
|
|
||||||
|
assertTrue(channel().isOpen());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maxRstCountSent_exceedsLimit_fails() throws Exception {
|
||||||
|
maxRstCount = 10;
|
||||||
|
maxRstPeriodNanos = TimeUnit.MILLISECONDS.toNanos(100);
|
||||||
|
manualSetUp();
|
||||||
|
assertThrows(ClosedChannelException.class, () -> madeYouReset(maxRstCount + 1));
|
||||||
|
|
||||||
|
assertFalse(channel().isOpen());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void madeYouReset(int burstSize) throws Exception {
|
||||||
|
when(streamTracerFactory.newServerStreamTracer(anyString(), any(Metadata.class)))
|
||||||
|
.thenAnswer((args) -> new TestServerStreamTracer());
|
||||||
|
Http2Headers headers = new DefaultHttp2Headers()
|
||||||
|
.method(HTTP_METHOD)
|
||||||
|
.set(CONTENT_TYPE_HEADER, new AsciiString("application/grpc", UTF_8))
|
||||||
|
.set(TE_HEADER, TE_TRAILERS)
|
||||||
|
.path(new AsciiString("/foo/bar"));
|
||||||
|
int streamId = 1;
|
||||||
|
long rpcTimeNanos = maxRstPeriodNanos / 2 / burstSize;
|
||||||
|
for (int period = 0; period < 3; period++) {
|
||||||
|
for (int i = 0; i < burstSize; i++) {
|
||||||
|
channelRead(headersFrame(streamId, headers));
|
||||||
|
channelRead(windowUpdate(streamId, 0));
|
||||||
|
streamId += 2;
|
||||||
|
fakeClock().forwardNanos(rpcTimeNanos);
|
||||||
|
}
|
||||||
|
while (channel().readOutbound() != null) {}
|
||||||
|
fakeClock().forwardNanos(maxRstPeriodNanos - rpcTimeNanos * burstSize + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void createStream() throws Exception {
|
private void createStream() throws Exception {
|
||||||
Http2Headers headers = new DefaultHttp2Headers()
|
Http2Headers headers = new DefaultHttp2Headers()
|
||||||
.method(HTTP_METHOD)
|
.method(HTTP_METHOD)
|
||||||
|
|
|
@ -12,7 +12,7 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
# GRPC_DEPS_START
|
# GRPC_DEPS_START
|
||||||
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||||
"com.google.android:annotations:4.1.1.4",
|
"com.google.android:annotations:4.1.1.4",
|
||||||
"com.google.api.grpc:proto-google-common-protos:2.51.0",
|
"com.google.api.grpc:proto-google-common-protos:2.59.2",
|
||||||
"com.google.auth:google-auth-library-credentials:1.24.1",
|
"com.google.auth:google-auth-library-credentials:1.24.1",
|
||||||
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
||||||
"com.google.auto.value:auto-value-annotations:1.11.0",
|
"com.google.auto.value:auto-value-annotations:1.11.0",
|
||||||
|
@ -23,24 +23,24 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||||
"com.google.guava:failureaccess:1.0.1",
|
"com.google.guava:failureaccess:1.0.1",
|
||||||
"com.google.guava:guava:33.3.1-android",
|
"com.google.guava:guava:33.3.1-android",
|
||||||
"com.google.re2j:re2j:1.8",
|
"com.google.re2j:re2j:1.8",
|
||||||
"com.google.s2a.proto.v2:s2a-proto:0.1.1",
|
"com.google.s2a.proto.v2:s2a-proto:0.1.2",
|
||||||
"com.google.truth:truth:1.4.2",
|
"com.google.truth:truth:1.4.2",
|
||||||
"com.squareup.okhttp:okhttp:2.7.5",
|
"com.squareup.okhttp:okhttp:2.7.5",
|
||||||
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
||||||
"io.netty:netty-buffer:4.1.110.Final",
|
"io.netty:netty-buffer:4.1.124.Final",
|
||||||
"io.netty:netty-codec-http2:4.1.110.Final",
|
"io.netty:netty-codec-http2:4.1.124.Final",
|
||||||
"io.netty:netty-codec-http:4.1.110.Final",
|
"io.netty:netty-codec-http:4.1.124.Final",
|
||||||
"io.netty:netty-codec-socks:4.1.110.Final",
|
"io.netty:netty-codec-socks:4.1.124.Final",
|
||||||
"io.netty:netty-codec:4.1.110.Final",
|
"io.netty:netty-codec:4.1.124.Final",
|
||||||
"io.netty:netty-common:4.1.110.Final",
|
"io.netty:netty-common:4.1.124.Final",
|
||||||
"io.netty:netty-handler-proxy:4.1.110.Final",
|
"io.netty:netty-handler-proxy:4.1.124.Final",
|
||||||
"io.netty:netty-handler:4.1.110.Final",
|
"io.netty:netty-handler:4.1.124.Final",
|
||||||
"io.netty:netty-resolver:4.1.110.Final",
|
"io.netty:netty-resolver:4.1.124.Final",
|
||||||
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
||||||
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
||||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final",
|
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.124.Final",
|
||||||
"io.netty:netty-transport-native-unix-common:4.1.110.Final",
|
"io.netty:netty-transport-native-unix-common:4.1.124.Final",
|
||||||
"io.netty:netty-transport:4.1.110.Final",
|
"io.netty:netty-transport:4.1.124.Final",
|
||||||
"io.opencensus:opencensus-api:0.31.0",
|
"io.opencensus:opencensus-api:0.31.0",
|
||||||
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
||||||
"io.perfmark:perfmark-api:0.27.0",
|
"io.perfmark:perfmark-api:0.27.0",
|
||||||
|
@ -116,9 +116,9 @@ def com_google_protobuf():
|
||||||
# This statement defines the @com_google_protobuf repo.
|
# This statement defines the @com_google_protobuf repo.
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "com_google_protobuf",
|
name = "com_google_protobuf",
|
||||||
sha256 = "9bd87b8280ef720d3240514f884e56a712f2218f0d693b48050c836028940a42",
|
sha256 = "3cf7d5b17c4ff04fe9f038104e9d0cae6da09b8ce271c13e44f8ac69f51e4e0f",
|
||||||
strip_prefix = "protobuf-25.1",
|
strip_prefix = "protobuf-25.5",
|
||||||
urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v25.1/protobuf-25.1.tar.gz"],
|
urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v25.5/protobuf-25.5.tar.gz"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def io_grpc_grpc_proto():
|
def io_grpc_grpc_proto():
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ public final class BlockingClientCall<ReqT, RespT> {
|
||||||
private final ThreadSafeThreadlessExecutor executor;
|
private final ThreadSafeThreadlessExecutor executor;
|
||||||
|
|
||||||
private boolean writeClosed;
|
private boolean writeClosed;
|
||||||
private volatile Status closedStatus; // null if not closed
|
private AtomicReference<CloseState> closeState = new AtomicReference<>();
|
||||||
|
|
||||||
BlockingClientCall(ClientCall<ReqT, RespT> call, ThreadSafeThreadlessExecutor executor) {
|
BlockingClientCall(ClientCall<ReqT, RespT> call, ThreadSafeThreadlessExecutor executor) {
|
||||||
this.call = call;
|
this.call = call;
|
||||||
|
@ -120,22 +121,22 @@ public final class BlockingClientCall<ReqT, RespT> {
|
||||||
logger.finer("Client Blocking read had value: " + bufferedValue);
|
logger.finer("Client Blocking read had value: " + bufferedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
Status currentClosedStatus;
|
CloseState currentCloseState;
|
||||||
if (bufferedValue != null) {
|
if (bufferedValue != null) {
|
||||||
call.request(1);
|
call.request(1);
|
||||||
return bufferedValue;
|
return bufferedValue;
|
||||||
} else if ((currentClosedStatus = closedStatus) == null) {
|
} else if ((currentCloseState = closeState.get()) == null) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"The message disappeared... are you reading from multiple threads?");
|
"The message disappeared... are you reading from multiple threads?");
|
||||||
} else if (!currentClosedStatus.isOk()) {
|
} else if (!currentCloseState.status.isOk()) {
|
||||||
throw currentClosedStatus.asException();
|
throw currentCloseState.status.asException(currentCloseState.trailers);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean skipWaitingForRead() {
|
boolean skipWaitingForRead() {
|
||||||
return closedStatus != null || !buffer.isEmpty();
|
return closeState.get() != null || !buffer.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,11 +149,11 @@ public final class BlockingClientCall<ReqT, RespT> {
|
||||||
* @throws StatusException If the stream was closed in an error state
|
* @throws StatusException If the stream was closed in an error state
|
||||||
*/
|
*/
|
||||||
public boolean hasNext() throws InterruptedException, StatusException {
|
public boolean hasNext() throws InterruptedException, StatusException {
|
||||||
executor.waitAndDrain((x) -> !x.buffer.isEmpty() || x.closedStatus != null, this);
|
executor.waitAndDrain((x) -> !x.buffer.isEmpty() || x.closeState.get() != null, this);
|
||||||
|
|
||||||
Status currentClosedStatus = closedStatus;
|
CloseState currentCloseState = closeState.get();
|
||||||
if (currentClosedStatus != null && !currentClosedStatus.isOk()) {
|
if (currentCloseState != null && !currentCloseState.status.isOk()) {
|
||||||
throw currentClosedStatus.asException();
|
throw currentCloseState.status.asException(currentCloseState.trailers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !buffer.isEmpty();
|
return !buffer.isEmpty();
|
||||||
|
@ -221,17 +222,16 @@ public final class BlockingClientCall<ReqT, RespT> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate<BlockingClientCall<ReqT, RespT>> predicate =
|
Predicate<BlockingClientCall<ReqT, RespT>> predicate =
|
||||||
(x) -> x.call.isReady() || x.closedStatus != null;
|
(x) -> x.call.isReady() || x.closeState.get() != null;
|
||||||
executor.waitAndDrainWithTimeout(waitForever, endNanoTime, predicate, this);
|
executor.waitAndDrainWithTimeout(waitForever, endNanoTime, predicate, this);
|
||||||
Status savedClosedStatus = closedStatus;
|
CloseState savedCloseState = closeState.get();
|
||||||
if (savedClosedStatus == null) {
|
if (savedCloseState == null || savedCloseState.status == null) {
|
||||||
call.sendMessage(request);
|
call.sendMessage(request);
|
||||||
return true;
|
return true;
|
||||||
} else if (savedClosedStatus.isOk()) {
|
} else if (savedCloseState.status.isOk()) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// Propagate any errors returned from the server
|
throw savedCloseState.status.asException(savedCloseState.trailers);
|
||||||
throw savedClosedStatus.asException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +274,8 @@ public final class BlockingClientCall<ReqT, RespT> {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
Status getClosedStatus() {
|
Status getClosedStatus() {
|
||||||
drainQuietly();
|
drainQuietly();
|
||||||
return closedStatus;
|
CloseState state = closeState.get();
|
||||||
|
return (state == null) ? null : state.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -317,7 +318,7 @@ public final class BlockingClientCall<ReqT, RespT> {
|
||||||
* @return True if writes haven't been closed and the server hasn't closed the stream
|
* @return True if writes haven't been closed and the server hasn't closed the stream
|
||||||
*/
|
*/
|
||||||
private boolean isWriteLegal() {
|
private boolean isWriteLegal() {
|
||||||
return !writeClosed && closedStatus == null;
|
return !writeClosed && closeState.get() == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientCall.Listener<RespT> getListener() {
|
ClientCall.Listener<RespT> getListener() {
|
||||||
|
@ -335,15 +336,25 @@ public final class BlockingClientCall<ReqT, RespT> {
|
||||||
private final class QueuingListener extends ClientCall.Listener<RespT> {
|
private final class QueuingListener extends ClientCall.Listener<RespT> {
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(RespT value) {
|
public void onMessage(RespT value) {
|
||||||
Preconditions.checkState(closedStatus == null, "ClientCall already closed");
|
Preconditions.checkState(closeState.get() == null, "ClientCall already closed");
|
||||||
buffer.add(value);
|
buffer.add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClose(Status status, Metadata trailers) {
|
public void onClose(Status status, Metadata trailers) {
|
||||||
Preconditions.checkState(closedStatus == null, "ClientCall already closed");
|
CloseState newCloseState = new CloseState(status, trailers);
|
||||||
closedStatus = status;
|
boolean wasSet = closeState.compareAndSet(null, newCloseState);
|
||||||
|
Preconditions.checkState(wasSet, "ClientCall already closed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class CloseState {
|
||||||
|
final Status status;
|
||||||
|
final Metadata trailers;
|
||||||
|
|
||||||
|
CloseState(Status status, Metadata trailers) {
|
||||||
|
this.status = Preconditions.checkNotNull(status, "status");
|
||||||
|
this.trailers = trailers;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,13 +320,14 @@ final class PriorityLoadBalancer extends LoadBalancer {
|
||||||
if (!children.containsKey(priority)) {
|
if (!children.containsKey(priority)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ConnectivityState oldState = connectivityState;
|
||||||
connectivityState = newState;
|
connectivityState = newState;
|
||||||
picker = newPicker;
|
picker = newPicker;
|
||||||
|
|
||||||
if (deletionTimer != null && deletionTimer.isPending()) {
|
if (deletionTimer != null && deletionTimer.isPending()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (newState.equals(CONNECTING)) {
|
if (newState.equals(CONNECTING) && !oldState.equals(newState)) {
|
||||||
if (!failOverTimer.isPending() && seenReadyOrIdleSinceTransientFailure) {
|
if (!failOverTimer.isPending() && seenReadyOrIdleSinceTransientFailure) {
|
||||||
failOverTimer = syncContext.schedule(new FailOverTask(), 10, TimeUnit.SECONDS,
|
failOverTimer = syncContext.schedule(new FailOverTask(), 10, TimeUnit.SECONDS,
|
||||||
executor);
|
executor);
|
||||||
|
|
|
@ -48,6 +48,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
@ -738,6 +739,32 @@ final class WeightedRoundRobinLoadBalancer extends MultiChildLoadBalancer {
|
||||||
this.errorUtilizationPenalty = errorUtilizationPenalty;
|
this.errorUtilizationPenalty = errorUtilizationPenalty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof WeightedRoundRobinLoadBalancerConfig)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
WeightedRoundRobinLoadBalancerConfig that = (WeightedRoundRobinLoadBalancerConfig) o;
|
||||||
|
return this.blackoutPeriodNanos == that.blackoutPeriodNanos
|
||||||
|
&& this.weightExpirationPeriodNanos == that.weightExpirationPeriodNanos
|
||||||
|
&& this.enableOobLoadReport == that.enableOobLoadReport
|
||||||
|
&& this.oobReportingPeriodNanos == that.oobReportingPeriodNanos
|
||||||
|
&& this.weightUpdatePeriodNanos == that.weightUpdatePeriodNanos
|
||||||
|
// Float.compare considers NaNs equal
|
||||||
|
&& Float.compare(this.errorUtilizationPenalty, that.errorUtilizationPenalty) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
blackoutPeriodNanos,
|
||||||
|
weightExpirationPeriodNanos,
|
||||||
|
enableOobLoadReport,
|
||||||
|
oobReportingPeriodNanos,
|
||||||
|
weightUpdatePeriodNanos,
|
||||||
|
errorUtilizationPenalty);
|
||||||
|
}
|
||||||
|
|
||||||
static final class Builder {
|
static final class Builder {
|
||||||
long blackoutPeriodNanos = 10_000_000_000L; // 10s
|
long blackoutPeriodNanos = 10_000_000_000L; // 10s
|
||||||
long weightExpirationPeriodNanos = 180_000_000_000L; //3min
|
long weightExpirationPeriodNanos = 180_000_000_000L; //3min
|
||||||
|
|
|
@ -109,7 +109,7 @@ final class ControlPlaneClient {
|
||||||
this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
|
this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
|
||||||
this.messagePrinter = checkNotNull(messagePrinter, "messagePrinter");
|
this.messagePrinter = checkNotNull(messagePrinter, "messagePrinter");
|
||||||
stopwatch = checkNotNull(stopwatchSupplier, "stopwatchSupplier").get();
|
stopwatch = checkNotNull(stopwatchSupplier, "stopwatchSupplier").get();
|
||||||
logId = InternalLogId.allocate("xds-client", serverInfo.target());
|
logId = InternalLogId.allocate("xds-cp-client", serverInfo.target());
|
||||||
logger = XdsLogger.withLogId(logId);
|
logger = XdsLogger.withLogId(logId);
|
||||||
logger.log(XdsLogLevel.INFO, "Created");
|
logger.log(XdsLogLevel.INFO, "Created");
|
||||||
}
|
}
|
||||||
|
|
|
@ -676,6 +676,8 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
||||||
private ResourceMetadata metadata;
|
private ResourceMetadata metadata;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String errorDescription;
|
private String errorDescription;
|
||||||
|
@Nullable
|
||||||
|
private Status lastError;
|
||||||
|
|
||||||
ResourceSubscriber(XdsResourceType<T> type, String resource) {
|
ResourceSubscriber(XdsResourceType<T> type, String resource) {
|
||||||
syncContext.throwIfNotInThisSynchronizationContext();
|
syncContext.throwIfNotInThisSynchronizationContext();
|
||||||
|
@ -712,11 +714,16 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
||||||
watchers.put(watcher, watcherExecutor);
|
watchers.put(watcher, watcherExecutor);
|
||||||
T savedData = data;
|
T savedData = data;
|
||||||
boolean savedAbsent = absent;
|
boolean savedAbsent = absent;
|
||||||
|
Status savedError = lastError;
|
||||||
watcherExecutor.execute(() -> {
|
watcherExecutor.execute(() -> {
|
||||||
if (errorDescription != null) {
|
if (errorDescription != null) {
|
||||||
watcher.onError(Status.INVALID_ARGUMENT.withDescription(errorDescription));
|
watcher.onError(Status.INVALID_ARGUMENT.withDescription(errorDescription));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (savedError != null) {
|
||||||
|
watcher.onError(savedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (savedData != null) {
|
if (savedData != null) {
|
||||||
notifyWatcher(watcher, savedData);
|
notifyWatcher(watcher, savedData);
|
||||||
} else if (savedAbsent) {
|
} else if (savedAbsent) {
|
||||||
|
@ -808,6 +815,7 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
||||||
this.metadata = ResourceMetadata
|
this.metadata = ResourceMetadata
|
||||||
.newResourceMetadataAcked(parsedResource.getRawResource(), version, updateTime);
|
.newResourceMetadataAcked(parsedResource.getRawResource(), version, updateTime);
|
||||||
absent = false;
|
absent = false;
|
||||||
|
lastError = null;
|
||||||
if (resourceDeletionIgnored) {
|
if (resourceDeletionIgnored) {
|
||||||
logger.log(XdsLogLevel.FORCE_INFO, "xds server {0}: server returned new version "
|
logger.log(XdsLogLevel.FORCE_INFO, "xds server {0}: server returned new version "
|
||||||
+ "of resource for which we previously ignored a deletion: type {1} name {2}",
|
+ "of resource for which we previously ignored a deletion: type {1} name {2}",
|
||||||
|
@ -857,6 +865,7 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
||||||
if (!absent) {
|
if (!absent) {
|
||||||
data = null;
|
data = null;
|
||||||
absent = true;
|
absent = true;
|
||||||
|
lastError = null;
|
||||||
metadata = serverInfo.resourceTimerIsTransientError()
|
metadata = serverInfo.resourceTimerIsTransientError()
|
||||||
? ResourceMetadata.newResourceMetadataTimeout()
|
? ResourceMetadata.newResourceMetadataTimeout()
|
||||||
: ResourceMetadata.newResourceMetadataDoesNotExist();
|
: ResourceMetadata.newResourceMetadataDoesNotExist();
|
||||||
|
@ -894,6 +903,7 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
||||||
Status errorAugmented = Status.fromCode(error.getCode())
|
Status errorAugmented = Status.fromCode(error.getCode())
|
||||||
.withDescription(description + "nodeID: " + bootstrapInfo.node().getId())
|
.withDescription(description + "nodeID: " + bootstrapInfo.node().getId())
|
||||||
.withCause(error.getCause());
|
.withCause(error.getCause());
|
||||||
|
this.lastError = errorAugmented;
|
||||||
|
|
||||||
for (ResourceWatcher<T> watcher : watchers.keySet()) {
|
for (ResourceWatcher<T> watcher : watchers.keySet()) {
|
||||||
if (tracker != null) {
|
if (tracker != null) {
|
||||||
|
|
|
@ -285,6 +285,8 @@ public abstract class GrpcXdsClientImplTestBase {
|
||||||
@Mock
|
@Mock
|
||||||
private ResourceWatcher<LdsUpdate> ldsResourceWatcher;
|
private ResourceWatcher<LdsUpdate> ldsResourceWatcher;
|
||||||
@Mock
|
@Mock
|
||||||
|
private ResourceWatcher<LdsUpdate> ldsResourceWatcher2;
|
||||||
|
@Mock
|
||||||
private ResourceWatcher<RdsUpdate> rdsResourceWatcher;
|
private ResourceWatcher<RdsUpdate> rdsResourceWatcher;
|
||||||
@Mock
|
@Mock
|
||||||
private ResourceWatcher<CdsUpdate> cdsResourceWatcher;
|
private ResourceWatcher<CdsUpdate> cdsResourceWatcher;
|
||||||
|
@ -694,6 +696,24 @@ public abstract class GrpcXdsClientImplTestBase {
|
||||||
assertThat(resourceDiscoveryCalls.poll()).isNull();
|
assertThat(resourceDiscoveryCalls.poll()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ldsResource_onError_cachedForNewWatcher() {
|
||||||
|
xdsClient.watchXdsResource(XdsListenerResource.getInstance(), LDS_RESOURCE,
|
||||||
|
ldsResourceWatcher);
|
||||||
|
DiscoveryRpcCall call = resourceDiscoveryCalls.poll();
|
||||||
|
call.sendCompleted();
|
||||||
|
verify(ldsResourceWatcher).onError(errorCaptor.capture());
|
||||||
|
Status initialError = errorCaptor.getValue();
|
||||||
|
xdsClient.watchXdsResource(XdsListenerResource.getInstance(), LDS_RESOURCE,
|
||||||
|
ldsResourceWatcher2);
|
||||||
|
ArgumentCaptor<Status> secondErrorCaptor = ArgumentCaptor.forClass(Status.class);
|
||||||
|
verify(ldsResourceWatcher2).onError(secondErrorCaptor.capture());
|
||||||
|
Status cachedError = secondErrorCaptor.getValue();
|
||||||
|
|
||||||
|
assertThat(cachedError).isEqualTo(initialError);
|
||||||
|
assertThat(resourceDiscoveryCalls.poll()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void ldsResponseErrorHandling_allResourcesFailedUnpack() {
|
public void ldsResponseErrorHandling_allResourcesFailedUnpack() {
|
||||||
DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE,
|
DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE,
|
||||||
|
|
|
@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.isA;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.clearInvocations;
|
import static org.mockito.Mockito.clearInvocations;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
|
@ -70,6 +71,7 @@ import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnit;
|
import org.mockito.junit.MockitoJUnit;
|
||||||
import org.mockito.junit.MockitoRule;
|
import org.mockito.junit.MockitoRule;
|
||||||
|
@ -554,6 +556,55 @@ public class PriorityLoadBalancerTest {
|
||||||
assertThat(fooHelpers).hasSize(2);
|
assertThat(fooHelpers).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void failoverTimerNotRestartedOnDupConnecting() {
|
||||||
|
InOrder inOrder = inOrder(helper);
|
||||||
|
PriorityChildConfig priorityChildConfig0 =
|
||||||
|
new PriorityChildConfig(newChildConfig(fooLbProvider, new Object()), true);
|
||||||
|
PriorityChildConfig priorityChildConfig1 =
|
||||||
|
new PriorityChildConfig(newChildConfig(fooLbProvider, new Object()), true);
|
||||||
|
PriorityLbConfig priorityLbConfig =
|
||||||
|
new PriorityLbConfig(
|
||||||
|
ImmutableMap.of("p0", priorityChildConfig0, "p1", priorityChildConfig1),
|
||||||
|
ImmutableList.of("p0", "p1"));
|
||||||
|
priorityLb.acceptResolvedAddresses(
|
||||||
|
ResolvedAddresses.newBuilder()
|
||||||
|
.setAddresses(ImmutableList.<EquivalentAddressGroup>of())
|
||||||
|
.setLoadBalancingPolicyConfig(priorityLbConfig)
|
||||||
|
.build());
|
||||||
|
// Nothing important about this verify, other than to provide a baseline
|
||||||
|
inOrder.verify(helper)
|
||||||
|
.updateBalancingState(eq(CONNECTING), pickerReturns(PickResult.withNoResult()));
|
||||||
|
assertThat(fooBalancers).hasSize(1);
|
||||||
|
assertThat(fooHelpers).hasSize(1);
|
||||||
|
Helper helper0 = Iterables.getOnlyElement(fooHelpers);
|
||||||
|
|
||||||
|
// Cause seenReadyOrIdleSinceTransientFailure = true
|
||||||
|
helper0.updateBalancingState(IDLE, EMPTY_PICKER);
|
||||||
|
inOrder.verify(helper)
|
||||||
|
.updateBalancingState(eq(IDLE), pickerReturns(PickResult.withNoResult()));
|
||||||
|
helper0.updateBalancingState(CONNECTING, EMPTY_PICKER);
|
||||||
|
|
||||||
|
// p0 keeps repeating CONNECTING, failover happens
|
||||||
|
fakeClock.forwardTime(5, TimeUnit.SECONDS);
|
||||||
|
helper0.updateBalancingState(CONNECTING, EMPTY_PICKER);
|
||||||
|
fakeClock.forwardTime(5, TimeUnit.SECONDS);
|
||||||
|
assertThat(fooBalancers).hasSize(2);
|
||||||
|
assertThat(fooHelpers).hasSize(2);
|
||||||
|
inOrder.verify(helper, times(2))
|
||||||
|
.updateBalancingState(eq(CONNECTING), pickerReturns(PickResult.withNoResult()));
|
||||||
|
Helper helper1 = Iterables.getLast(fooHelpers);
|
||||||
|
|
||||||
|
// p0 keeps repeating CONNECTING, no reset of failover timer
|
||||||
|
helper1.updateBalancingState(IDLE, EMPTY_PICKER); // Stop timer for p1
|
||||||
|
inOrder.verify(helper)
|
||||||
|
.updateBalancingState(eq(IDLE), pickerReturns(PickResult.withNoResult()));
|
||||||
|
helper0.updateBalancingState(CONNECTING, EMPTY_PICKER);
|
||||||
|
fakeClock.forwardTime(10, TimeUnit.SECONDS);
|
||||||
|
inOrder.verify(helper, never())
|
||||||
|
.updateBalancingState(eq(CONNECTING), any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readyToConnectDoesNotFailOverButUpdatesPicker() {
|
public void readyToConnectDoesNotFailOverButUpdatesPicker() {
|
||||||
PriorityChildConfig priorityChildConfig0 =
|
PriorityChildConfig priorityChildConfig0 =
|
||||||
|
|
|
@ -35,6 +35,7 @@ import com.github.xds.service.orca.v3.OrcaLoadReportRequest;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.testing.EqualsTester;
|
||||||
import com.google.protobuf.Duration;
|
import com.google.protobuf.Duration;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.CallOptions;
|
import io.grpc.CallOptions;
|
||||||
|
@ -215,6 +216,40 @@ public class WeightedRoundRobinLoadBalancerTest {
|
||||||
weightedPicker.pickSubchannel(mockArgs);
|
weightedPicker.pickSubchannel(mockArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void config_equalsTester() {
|
||||||
|
WeightedRoundRobinLoadBalancerConfig defaults =
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder().build();
|
||||||
|
new EqualsTester()
|
||||||
|
.addEqualityGroup(
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder().build(),
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder().build(),
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder()
|
||||||
|
.setBlackoutPeriodNanos(defaults.blackoutPeriodNanos).build())
|
||||||
|
.addEqualityGroup(
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder()
|
||||||
|
.setBlackoutPeriodNanos(5).build())
|
||||||
|
.addEqualityGroup(
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder()
|
||||||
|
.setWeightExpirationPeriodNanos(5).build())
|
||||||
|
.addEqualityGroup(
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder()
|
||||||
|
.setEnableOobLoadReport(true).build())
|
||||||
|
.addEqualityGroup(
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder()
|
||||||
|
.setOobReportingPeriodNanos(5).build())
|
||||||
|
.addEqualityGroup(
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder()
|
||||||
|
.setWeightUpdatePeriodNanos(5).build())
|
||||||
|
.addEqualityGroup(
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder()
|
||||||
|
.setErrorUtilizationPenalty(0.5F).build())
|
||||||
|
.addEqualityGroup(
|
||||||
|
WeightedRoundRobinLoadBalancerConfig.newBuilder()
|
||||||
|
.setErrorUtilizationPenalty(Float.NaN).build())
|
||||||
|
.testEquals();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void wrrLifeCycle() {
|
public void wrrLifeCycle() {
|
||||||
syncContext.execute(() -> wrr.acceptResolvedAddresses(ResolvedAddresses.newBuilder()
|
syncContext.execute(() -> wrr.acceptResolvedAddresses(ResolvedAddresses.newBuilder()
|
||||||
|
|
Loading…
Reference in New Issue