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",
|
||||
compatibility_level = 0,
|
||||
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
|
||||
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||
"com.google.android:annotations:4.1.1.4",
|
||||
"com.google.api.grpc:proto-google-common-protos:2.51.0",
|
||||
"com.google.api.grpc:proto-google-common-protos:2.59.2",
|
||||
"com.google.auth:google-auth-library-credentials:1.24.1",
|
||||
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
||||
"com.google.auto.value:auto-value-annotations:1.11.0",
|
||||
|
@ -19,24 +19,24 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
|||
"com.google.guava:failureaccess:1.0.1",
|
||||
"com.google.guava:guava:33.3.1-android",
|
||||
"com.google.re2j:re2j:1.8",
|
||||
"com.google.s2a.proto.v2:s2a-proto:0.1.1",
|
||||
"com.google.s2a.proto.v2:s2a-proto:0.1.2",
|
||||
"com.google.truth:truth:1.4.2",
|
||||
"com.squareup.okhttp:okhttp:2.7.5",
|
||||
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
||||
"io.netty:netty-buffer:4.1.110.Final",
|
||||
"io.netty:netty-codec-http2:4.1.110.Final",
|
||||
"io.netty:netty-codec-http:4.1.110.Final",
|
||||
"io.netty:netty-codec-socks:4.1.110.Final",
|
||||
"io.netty:netty-codec:4.1.110.Final",
|
||||
"io.netty:netty-common:4.1.110.Final",
|
||||
"io.netty:netty-handler-proxy:4.1.110.Final",
|
||||
"io.netty:netty-handler:4.1.110.Final",
|
||||
"io.netty:netty-resolver:4.1.110.Final",
|
||||
"io.netty:netty-buffer:4.1.124.Final",
|
||||
"io.netty:netty-codec-http2:4.1.124.Final",
|
||||
"io.netty:netty-codec-http:4.1.124.Final",
|
||||
"io.netty:netty-codec-socks:4.1.124.Final",
|
||||
"io.netty:netty-codec:4.1.124.Final",
|
||||
"io.netty:netty-common:4.1.124.Final",
|
||||
"io.netty:netty-handler-proxy:4.1.124.Final",
|
||||
"io.netty:netty-handler:4.1.124.Final",
|
||||
"io.netty:netty-resolver:4.1.124.Final",
|
||||
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
||||
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.110.Final",
|
||||
"io.netty:netty-transport:4.1.110.Final",
|
||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.124.Final",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.124.Final",
|
||||
"io.netty:netty-transport:4.1.124.Final",
|
||||
"io.opencensus:opencensus-api:0.31.0",
|
||||
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
||||
"io.perfmark:perfmark-api:0.27.0",
|
||||
|
@ -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 = "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 = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
|
||||
# Protobuf 25.5+ is incompatible with Bazel 7 with bzlmod
|
||||
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "24.4")
|
||||
bazel_dep(name = "rules_cc", version = "0.0.9")
|
||||
bazel_dep(name = "rules_java", version = "5.3.5")
|
||||
bazel_dep(name = "rules_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
|
||||
basics](https://grpc.io/docs/languages/java/basics).
|
||||
|
||||
The [examples](https://github.com/grpc/grpc-java/tree/v1.74.0/examples) and the
|
||||
[Android example](https://github.com/grpc/grpc-java/tree/v1.74.0/examples/android)
|
||||
The [examples](https://github.com/grpc/grpc-java/tree/v1.75.0/examples) and the
|
||||
[Android example](https://github.com/grpc/grpc-java/tree/v1.75.0/examples/android)
|
||||
are standalone projects that showcase the usage of gRPC.
|
||||
|
||||
Download
|
||||
|
@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
|||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty-shaded</artifactId>
|
||||
<version>1.74.0</version>
|
||||
<version>1.75.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
<version>1.74.0</version>
|
||||
<version>1.75.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
<version>1.74.0</version>
|
||||
<version>1.75.0</version>
|
||||
</dependency>
|
||||
<dependency> <!-- necessary for Java 9+ -->
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
|
@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
|
|||
|
||||
Or for Gradle with non-Android, add to your dependencies:
|
||||
```gradle
|
||||
runtimeOnly 'io.grpc:grpc-netty-shaded:1.74.0'
|
||||
implementation 'io.grpc:grpc-protobuf:1.74.0'
|
||||
implementation 'io.grpc:grpc-stub:1.74.0'
|
||||
runtimeOnly 'io.grpc:grpc-netty-shaded:1.75.0'
|
||||
implementation 'io.grpc:grpc-protobuf:1.75.0'
|
||||
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
||||
```
|
||||
|
||||
For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and
|
||||
`grpc-protobuf-lite` instead of `grpc-protobuf`:
|
||||
```gradle
|
||||
implementation 'io.grpc:grpc-okhttp:1.74.0'
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.74.0'
|
||||
implementation 'io.grpc:grpc-stub:1.74.0'
|
||||
implementation 'io.grpc:grpc-okhttp:1.75.0'
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0'
|
||||
implementation 'io.grpc:grpc-stub:1.75.0'
|
||||
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
|
||||
```
|
||||
|
||||
|
@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either
|
|||
(with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below).
|
||||
|
||||
[the JARs]:
|
||||
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.74.0
|
||||
https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.75.0
|
||||
|
||||
Development snapshots are available in [Sonatypes's snapshot
|
||||
repository](https://central.sonatype.com/repository/maven-snapshots/).
|
||||
|
@ -131,7 +131,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use
|
|||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.74.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.75.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -161,7 +161,7 @@ protobuf {
|
|||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.74.0'
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -194,7 +194,7 @@ protobuf {
|
|||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.74.0'
|
||||
artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0'
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
|
|
@ -400,7 +400,8 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver
|
|||
1.59.x | 4.1.97.Final | 2.0.61.Final
|
||||
1.60.x-1.66.x | 4.1.100.Final | 2.0.61.Final
|
||||
1.67.x-1.70.x | 4.1.110.Final | 2.0.65.Final
|
||||
1.71.x- | 4.1.110.Final | 2.0.70.Final
|
||||
1.71.x-1.74.x | 4.1.110.Final | 2.0.70.Final
|
||||
1.75.x- | 4.1.124.Final | 2.0.72.Final
|
||||
|
||||
_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ android {
|
|||
}
|
||||
compileSdkVersion 34
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
|
|
@ -217,7 +217,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
|||
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
||||
unregisterRunnable =
|
||||
new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void run() {
|
||||
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
||||
|
@ -231,7 +230,6 @@ public final class AndroidChannelBuilder extends ForwardingChannelBuilder<Androi
|
|||
context.registerReceiver(networkReceiver, networkIntentFilter);
|
||||
unregisterRunnable =
|
||||
new Runnable() {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void run() {
|
||||
context.unregisterReceiver(networkReceiver);
|
||||
|
|
|
@ -166,6 +166,11 @@ public final class NameResolverRegistry {
|
|||
} catch (ClassNotFoundException e) {
|
||||
logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
|
||||
}
|
||||
try {
|
||||
list.add(Class.forName("io.grpc.binder.internal.IntentNameResolverProvider"));
|
||||
} catch (ClassNotFoundException e) {
|
||||
logger.log(Level.FINE, "Unable to find IntentNameResolverProvider", e);
|
||||
}
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ android {
|
|||
targetCompatibility 1.8
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
<service android:name="io.grpc.binder.HostServices$HostService1" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="action1"/>
|
||||
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name="io.grpc.binder.HostServices$HostService2" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="action2"/>
|
||||
<data android:scheme="scheme" android:host="authority" android:path="/path"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
|
|
@ -23,6 +23,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
@ -39,7 +40,6 @@ import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
|||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.NameResolverRegistry;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCall.Listener;
|
||||
import io.grpc.ServerCallHandler;
|
||||
|
@ -49,7 +49,6 @@ import io.grpc.ServerServiceDefinition;
|
|||
import io.grpc.Status.Code;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.testing.FakeNameResolverProvider;
|
||||
import io.grpc.stub.ClientCalls;
|
||||
import io.grpc.stub.MetadataUtils;
|
||||
import io.grpc.stub.ServerCalls;
|
||||
|
@ -77,7 +76,6 @@ public final class BinderChannelSmokeTest {
|
|||
|
||||
private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100;
|
||||
private static final String MSG = "Some text which will be repeated many many times";
|
||||
private static final String SERVER_TARGET_URI = "fake://server";
|
||||
private static final Metadata.Key<PoisonParcelable> POISON_KEY =
|
||||
ParcelableUtils.metadataKey("poison-bin", PoisonParcelable.CREATOR);
|
||||
|
||||
|
@ -99,7 +97,6 @@ public final class BinderChannelSmokeTest {
|
|||
.setType(MethodDescriptor.MethodType.BIDI_STREAMING)
|
||||
.build();
|
||||
|
||||
FakeNameResolverProvider fakeNameResolverProvider;
|
||||
ManagedChannel channel;
|
||||
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
|
||||
AtomicReference<PeerUid> clientUidCapture = new AtomicReference<>();
|
||||
|
@ -138,8 +135,6 @@ public final class BinderChannelSmokeTest {
|
|||
PeerUids.newPeerIdentifyingServerInterceptor());
|
||||
|
||||
AndroidComponentAddress serverAddress = HostServices.allocateService(appContext);
|
||||
fakeNameResolverProvider = new FakeNameResolverProvider(SERVER_TARGET_URI, serverAddress);
|
||||
NameResolverRegistry.getDefaultRegistry().register(fakeNameResolverProvider);
|
||||
HostServices.configureService(
|
||||
serverAddress,
|
||||
HostServices.serviceParamsBuilder()
|
||||
|
@ -166,7 +161,6 @@ public final class BinderChannelSmokeTest {
|
|||
@After
|
||||
public void tearDown() throws Exception {
|
||||
channel.shutdownNow();
|
||||
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider);
|
||||
HostServices.awaitServiceShutdown();
|
||||
}
|
||||
|
||||
|
@ -235,7 +229,11 @@ public final class BinderChannelSmokeTest {
|
|||
|
||||
@Test
|
||||
public void testConnectViaTargetUri() throws Exception {
|
||||
channel = BinderChannelBuilder.forTarget(SERVER_TARGET_URI, appContext).build();
|
||||
// Compare with the <intent-filter> mapping in AndroidManifest.xml.
|
||||
channel =
|
||||
BinderChannelBuilder.forTarget(
|
||||
"intent://authority/path#Intent;action=action1;scheme=scheme;end;", appContext)
|
||||
.build();
|
||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||
}
|
||||
|
||||
|
@ -245,7 +243,10 @@ public final class BinderChannelSmokeTest {
|
|||
channel =
|
||||
BinderChannelBuilder.forAddress(
|
||||
AndroidComponentAddress.forBindIntent(
|
||||
new Intent().setAction("action1").setPackage(appContext.getPackageName())),
|
||||
new Intent()
|
||||
.setAction("action1")
|
||||
.setData(Uri.parse("scheme://authority/path"))
|
||||
.setPackage(appContext.getPackageName())),
|
||||
appContext)
|
||||
.build();
|
||||
assertThat(doCall("Hello").get()).isEqualTo("Hello");
|
||||
|
|
|
@ -100,7 +100,7 @@ public final class BinderClientTransportTest {
|
|||
.build();
|
||||
|
||||
AndroidComponentAddress serverAddress;
|
||||
BinderTransport.BinderClientTransport transport;
|
||||
BinderClientTransport transport;
|
||||
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
|
||||
|
||||
private final ObjectPool<ScheduledExecutorService> executorServicePool =
|
||||
|
@ -178,7 +178,7 @@ public final class BinderClientTransportTest {
|
|||
return this;
|
||||
}
|
||||
|
||||
public BinderTransport.BinderClientTransport build() {
|
||||
public BinderClientTransport build() {
|
||||
return factoryBuilder
|
||||
.buildClientTransportFactory()
|
||||
.newClientTransport(serverAddress, new ClientTransportOptions(), null);
|
||||
|
@ -502,8 +502,7 @@ public final class BinderClientTransportTest {
|
|||
}
|
||||
|
||||
private static void startAndAwaitReady(
|
||||
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
|
||||
throws Exception {
|
||||
BinderClientTransport transport, TestTransportListener transportListener) throws Exception {
|
||||
transport.start(transportListener).run();
|
||||
transportListener.awaitReady();
|
||||
}
|
||||
|
|
|
@ -106,8 +106,7 @@ public final class BinderTransportTest extends AbstractTransportTest {
|
|||
options.setEagAttributes(eagAttrs());
|
||||
options.setChannelLogger(transportLogger());
|
||||
|
||||
return new BinderTransport.BinderClientTransport(
|
||||
builder.buildClientTransportFactory(), addr, options);
|
||||
return new BinderClientTransport(builder.buildClientTransportFactory(), addr, options);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -34,6 +34,18 @@ public final class ApiConstants {
|
|||
*/
|
||||
public static final String ACTION_BIND = "grpc.io.action.BIND";
|
||||
|
||||
/**
|
||||
* Gives a {@link NameResolver} access to its Channel's "source" {@link android.content.Context},
|
||||
* the entry point to almost every other Android API.
|
||||
*
|
||||
* <p>This argument is set automatically by {@link BinderChannelBuilder}. Any value passed to
|
||||
* {@link io.grpc.ManagedChannelBuilder#setNameResolverArg} will be ignored.
|
||||
*
|
||||
* <p>See {@link BinderChannelBuilder#forTarget(String, android.content.Context)} for more.
|
||||
*/
|
||||
public static final NameResolver.Args.Key<android.content.Context> SOURCE_ANDROID_CONTEXT =
|
||||
NameResolver.Args.Key.create("source-android-context");
|
||||
|
||||
/**
|
||||
* Specifies the Android user in which target URIs should be resolved.
|
||||
*
|
||||
|
|
|
@ -67,4 +67,25 @@ public abstract class AsyncSecurityPolicy extends SecurityPolicy {
|
|||
* authorized.
|
||||
*/
|
||||
public abstract ListenableFuture<Status> checkAuthorizationAsync(int uid);
|
||||
|
||||
/**
|
||||
* Decides whether the given Android UID is authorized, without providing its raw integer value.
|
||||
*
|
||||
* <p>Calling this is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
|
||||
* the caller provides a {@link PeerUid} wrapper instead of the raw integer uid (known only to the
|
||||
* transport). This allows a server to check additional application-layer security policy for
|
||||
* itself *after* the call itself is authorized by the transport layer. Cross cutting application-
|
||||
* layer checks could be done from a {@link io.grpc.ServerInterceptor}. Checks based on the
|
||||
* substance of a request message could be done by the individual RPC method implementations
|
||||
* themselves.
|
||||
*
|
||||
* <p>See #checkAuthorizationAsync(int) for details on the semantics. See {@link
|
||||
* PeerUids#newPeerIdentifyingServerInterceptor()} for how to get a {@link PeerUid}.
|
||||
*
|
||||
* @param uid The Android UID to authenticate.
|
||||
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||
*/
|
||||
public final ListenableFuture<Status> checkAuthorizationAsync(PeerUid uid) {
|
||||
return checkAuthorizationAsync(uid.getUid());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -321,6 +321,8 @@ public final class BinderChannelBuilder extends ForwardingChannelBuilder<BinderC
|
|||
public ManagedChannel build() {
|
||||
transportFactoryBuilder.setOffloadExecutorPool(
|
||||
managedChannelImplBuilder.getOffloadExecutorPool());
|
||||
setNameResolverArg(
|
||||
ApiConstants.SOURCE_ANDROID_CONTEXT, transportFactoryBuilder.getSourceContext());
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,7 +184,6 @@ public final class SecurityPolicies {
|
|||
* Creates {@link SecurityPolicy} which checks if the app is a device owner app. See {@link
|
||||
* DevicePolicyManager}.
|
||||
*/
|
||||
@RequiresApi(18)
|
||||
public static io.grpc.binder.SecurityPolicy isDeviceOwner(Context applicationContext) {
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
|
@ -199,7 +198,6 @@ public final class SecurityPolicies {
|
|||
* Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See {@link
|
||||
* DevicePolicyManager}.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public static SecurityPolicy isProfileOwner(Context applicationContext) {
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
|
|
|
@ -53,4 +53,25 @@ public abstract class SecurityPolicy {
|
|||
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||
*/
|
||||
public abstract Status checkAuthorization(int uid);
|
||||
|
||||
/**
|
||||
* Decides whether the given Android UID is authorized, without providing its raw integer value.
|
||||
*
|
||||
* <p>Calling this is equivalent to calling {@link SecurityPolicy#checkAuthorization(int)}, except
|
||||
* the caller provides a {@link PeerUid} wrapper instead of the raw integer uid (known only to the
|
||||
* transport). This allows a server to check additional application-layer security policy for
|
||||
* itself *after* the call itself is authorized by the transport layer. Cross cutting application-
|
||||
* layer checks could be done from a {@link io.grpc.ServerInterceptor}. Checks based on the
|
||||
* substance of a request message could be done by the individual RPC method implementations
|
||||
* themselves.
|
||||
*
|
||||
* <p>See #checkAuthorizationAsync(int) for details on the semantics. See {@link
|
||||
* PeerUids#newPeerIdentifyingServerInterceptor()} for how to get a {@link PeerUid}.
|
||||
*
|
||||
* @param uid The Android UID to authenticate.
|
||||
* @return A gRPC {@link Status} object, with OK indicating authorized.
|
||||
*/
|
||||
public final Status checkAuthorization(PeerUid uid) {
|
||||
return checkAuthorization(uid.getUid());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ import io.grpc.internal.ServerTransport;
|
|||
import io.grpc.internal.ServerTransportListener;
|
||||
|
||||
/**
|
||||
* Tracks which {@link BinderTransport.BinderServerTransport} are currently active and allows
|
||||
* invoking a {@link Runnable} only once all transports are terminated.
|
||||
* Tracks which {@link BinderServerTransport} are currently active and allows invoking a {@link
|
||||
* Runnable} only once all transports are terminated.
|
||||
*/
|
||||
final class ActiveTransportTracker implements ServerListener {
|
||||
private final ServerListener delegate;
|
||||
|
|
|
@ -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
|
||||
public BinderTransport.BinderClientTransport newClientTransport(
|
||||
public BinderClientTransport newClientTransport(
|
||||
SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
|
||||
if (closed) {
|
||||
throw new IllegalStateException("The transport factory is closed.");
|
||||
}
|
||||
return new BinderTransport.BinderClientTransport(this, (AndroidComponentAddress) addr, options);
|
||||
return new BinderClientTransport(this, (AndroidComponentAddress) addr, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -142,6 +142,10 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
|
|||
return this;
|
||||
}
|
||||
|
||||
public Context getSourceContext() {
|
||||
return sourceContext;
|
||||
}
|
||||
|
||||
public Builder setOffloadExecutorPool(ObjectPool<? extends Executor> offloadExecutorPool) {
|
||||
this.offloadExecutorPool = checkNotNull(offloadExecutorPool, "offloadExecutorPool");
|
||||
return this;
|
||||
|
|
|
@ -178,8 +178,8 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder.
|
|||
serverPolicyChecker,
|
||||
checkNotNull(executor, "Not started?"));
|
||||
// Create a new transport and let our listener know about it.
|
||||
BinderTransport.BinderServerTransport transport =
|
||||
new BinderTransport.BinderServerTransport(
|
||||
BinderServerTransport transport =
|
||||
new BinderServerTransport(
|
||||
executorServicePool,
|
||||
attrsBuilder.build(),
|
||||
streamTracerFactories,
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2020 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import android.os.IBinder;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Internal;
|
||||
import io.grpc.InternalLogId;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerStreamTracer;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.internal.ObjectPool;
|
||||
import io.grpc.internal.ServerStream;
|
||||
import io.grpc.internal.ServerTransport;
|
||||
import io.grpc.internal.ServerTransportListener;
|
||||
import io.grpc.internal.StatsTraceContext;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Concrete server-side transport implementation. */
|
||||
@Internal
|
||||
public final class BinderServerTransport extends BinderTransport implements ServerTransport {
|
||||
|
||||
private final List<ServerStreamTracer.Factory> streamTracerFactories;
|
||||
@Nullable private ServerTransportListener serverTransportListener;
|
||||
|
||||
/**
|
||||
* Constructs a new transport instance.
|
||||
*
|
||||
* @param binderDecorator used to decorate 'callbackBinder', for fault injection.
|
||||
*/
|
||||
public BinderServerTransport(
|
||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||
Attributes attributes,
|
||||
List<ServerStreamTracer.Factory> streamTracerFactories,
|
||||
OneWayBinderProxy.Decorator binderDecorator,
|
||||
IBinder callbackBinder) {
|
||||
super(executorServicePool, attributes, binderDecorator, buildLogId(attributes));
|
||||
this.streamTracerFactories = streamTracerFactories;
|
||||
// TODO(jdcormie): Plumb in the Server's executor() and use it here instead.
|
||||
setOutgoingBinder(OneWayBinderProxy.wrap(callbackBinder, getScheduledExecutorService()));
|
||||
}
|
||||
|
||||
public synchronized void setServerTransportListener(
|
||||
ServerTransportListener serverTransportListener) {
|
||||
this.serverTransportListener = serverTransportListener;
|
||||
if (isShutdown()) {
|
||||
setState(TransportState.SHUTDOWN_TERMINATED);
|
||||
notifyTerminated();
|
||||
releaseExecutors();
|
||||
} else {
|
||||
sendSetupTransaction();
|
||||
// Check we're not shutdown again, since a failure inside sendSetupTransaction (or a callback
|
||||
// it triggers), could have shut us down.
|
||||
if (!isShutdown()) {
|
||||
setState(TransportState.READY);
|
||||
attributes = serverTransportListener.transportReady(attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatsTraceContext createStatsTraceContext(String methodName, Metadata headers) {
|
||||
return StatsTraceContext.newServerContext(streamTracerFactories, methodName, headers);
|
||||
}
|
||||
|
||||
synchronized Status startStream(ServerStream stream, String methodName, Metadata headers) {
|
||||
if (isShutdown()) {
|
||||
return Status.UNAVAILABLE.withDescription("transport is shutdown");
|
||||
} else {
|
||||
serverTransportListener.streamCreated(stream, methodName, headers);
|
||||
return Status.OK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
void notifyShutdown(Status status) {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("this")
|
||||
void notifyTerminated() {
|
||||
if (serverTransportListener != null) {
|
||||
serverTransportListener.transportTerminated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdown() {
|
||||
shutdownInternal(Status.OK, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdownNow(Status reason) {
|
||||
shutdownInternal(reason, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@GuardedBy("this")
|
||||
protected Inbound<?> createInbound(int callId) {
|
||||
return new Inbound.ServerInbound(this, attributes, callId);
|
||||
}
|
||||
|
||||
private static InternalLogId buildLogId(Attributes attributes) {
|
||||
return InternalLogId.allocate(
|
||||
BinderServerTransport.class, "from " + attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
|
||||
}
|
||||
}
|
|
@ -19,67 +19,32 @@ package io.grpc.binder.internal;
|
|||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
||||
import static 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.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.TransactionTooLargeException;
|
||||
import androidx.annotation.BinderThread;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.errorprone.annotations.CheckReturnValue;
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.ClientStreamTracer;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Internal;
|
||||
import io.grpc.InternalChannelz.SocketStats;
|
||||
import io.grpc.InternalLogId;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.SecurityLevel;
|
||||
import io.grpc.ServerStreamTracer;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import io.grpc.binder.AsyncSecurityPolicy;
|
||||
import io.grpc.binder.InboundParcelablePolicy;
|
||||
import io.grpc.binder.SecurityPolicy;
|
||||
import io.grpc.internal.ClientStream;
|
||||
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
|
||||
import io.grpc.internal.ConnectionClientTransport;
|
||||
import io.grpc.internal.FailingClientStream;
|
||||
import io.grpc.internal.GrpcAttributes;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.ManagedClientTransport;
|
||||
import io.grpc.internal.ObjectPool;
|
||||
import io.grpc.internal.ServerStream;
|
||||
import io.grpc.internal.ServerTransport;
|
||||
import io.grpc.internal.ServerTransportListener;
|
||||
import io.grpc.internal.StatsTraceContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -169,10 +134,10 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
|
|||
private static final int RESERVED_TRANSACTIONS = 1000;
|
||||
|
||||
/** The first call ID we can use. */
|
||||
private static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
|
||||
static final int FIRST_CALL_ID = IBinder.FIRST_CALL_TRANSACTION + RESERVED_TRANSACTIONS;
|
||||
|
||||
/** The last call ID we can use. */
|
||||
private static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
|
||||
static final int LAST_CALL_ID = IBinder.LAST_CALL_TRANSACTION;
|
||||
|
||||
/** The states of this transport. */
|
||||
protected enum TransportState {
|
||||
|
@ -218,7 +183,7 @@ public abstract class BinderTransport implements IBinder.DeathRecipient {
|
|||
// Only read/written on @BinderThread.
|
||||
private long acknowledgedIncomingBytes;
|
||||
|
||||
private BinderTransport(
|
||||
protected BinderTransport(
|
||||
ObjectPool<ScheduledExecutorService> executorServicePool,
|
||||
Attributes attributes,
|
||||
OneWayBinderProxy.Decorator binderDecorator,
|
||||
|
@ -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) {
|
||||
switch (next) {
|
||||
case SETUP:
|
||||
|
|
|
@ -610,10 +610,9 @@ abstract class Inbound<L extends StreamListener> implements StreamListener.Messa
|
|||
// Server-side inbound transactions.
|
||||
static final class ServerInbound extends Inbound<ServerStreamListener> {
|
||||
|
||||
private final BinderTransport.BinderServerTransport serverTransport;
|
||||
private final BinderServerTransport serverTransport;
|
||||
|
||||
ServerInbound(
|
||||
BinderTransport.BinderServerTransport transport, Attributes attributes, int callId) {
|
||||
ServerInbound(BinderServerTransport transport, Attributes attributes, int callId) {
|
||||
super(transport, attributes, callId);
|
||||
this.serverTransport = transport;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static io.grpc.binder.internal.SystemApis.createContextAsUser;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Build;
|
||||
import android.os.UserHandle;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.EquivalentAddressGroup;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import io.grpc.StatusOr;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import io.grpc.binder.ApiConstants;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link NameResolver} that resolves Android-standard "intent:" target URIs to the list of {@link
|
||||
* AndroidComponentAddress} that match it by manifest intent filter.
|
||||
*/
|
||||
final class IntentNameResolver extends NameResolver {
|
||||
private final Intent targetIntent; // Never mutated.
|
||||
@Nullable private final UserHandle targetUser; // null means same user that hosts this process.
|
||||
private final Context targetUserContext;
|
||||
private final Executor offloadExecutor;
|
||||
private final Executor sequentialExecutor;
|
||||
private final SynchronizationContext syncContext;
|
||||
private final ServiceConfigParser serviceConfigParser;
|
||||
|
||||
// Accessed only on `sequentialExecutor`
|
||||
@Nullable private PackageChangeReceiver receiver; // != null when registered
|
||||
|
||||
// Accessed only on 'syncContext'.
|
||||
private boolean shutdown;
|
||||
private boolean queryNeeded;
|
||||
@Nullable private Listener2 listener; // != null after start().
|
||||
@Nullable private ListenableFuture<ResolutionResult> queryResultFuture; // != null when querying.
|
||||
|
||||
@EquivalentAddressGroup.Attr
|
||||
private static final Attributes CONSTANT_EAG_ATTRS =
|
||||
Attributes.newBuilder()
|
||||
// Servers discovered in PackageManager are especially untrusted. After all, any app can
|
||||
// declare any intent filter it wants! Require pre-authorization so that unauthorized apps
|
||||
// don't even get a chance to run onCreate()/onBind().
|
||||
.set(ApiConstants.PRE_AUTH_SERVER_OVERRIDE, true)
|
||||
.build();
|
||||
|
||||
IntentNameResolver(Intent targetIntent, Args args) {
|
||||
this.targetIntent = targetIntent;
|
||||
this.targetUser = args.getArg(ApiConstants.TARGET_ANDROID_USER);
|
||||
Context context =
|
||||
checkNotNull(args.getArg(ApiConstants.SOURCE_ANDROID_CONTEXT), "SOURCE_ANDROID_CONTEXT")
|
||||
.getApplicationContext();
|
||||
this.targetUserContext =
|
||||
targetUser != null ? createContextForTargetUserOrThrow(context, targetUser) : context;
|
||||
// This Executor is nominally optional but all grpc-java Channels provide it since 1.25.
|
||||
this.offloadExecutor =
|
||||
checkNotNull(args.getOffloadExecutor(), "NameResolver.Args.getOffloadExecutor()");
|
||||
// Ensures start()'s work runs before resolve()'s' work, and both run before shutdown()'s.
|
||||
this.sequentialExecutor = MoreExecutors.newSequentialExecutor(offloadExecutor);
|
||||
this.syncContext = args.getSynchronizationContext();
|
||||
this.serviceConfigParser = args.getServiceConfigParser();
|
||||
}
|
||||
|
||||
private static Context createContextForTargetUserOrThrow(Context context, UserHandle targetUser) {
|
||||
try {
|
||||
return createContextAsUser(context, targetUser, /* flags= */ 0); // @SystemApi since R.
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"TARGET_ANDROID_USER NameResolver.Arg requires SDK_INT >= R and @SystemApi visibility");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Listener2 listener) {
|
||||
checkState(this.listener == null, "Already started!");
|
||||
checkState(!shutdown, "Resolver is shutdown");
|
||||
this.listener = checkNotNull(listener);
|
||||
sequentialExecutor.execute(this::registerReceiver);
|
||||
resolve();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
checkState(listener != null, "Not started!");
|
||||
resolve();
|
||||
}
|
||||
|
||||
private void resolve() {
|
||||
syncContext.throwIfNotInThisSynchronizationContext();
|
||||
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't block here in 'syncContext' so we offload PackageManager queries to an Executor.
|
||||
// But offloading complicates things a bit because other calls can arrive while we wait for the
|
||||
// results. We keep 'listener' up-to-date with the latest state in PackageManager by doing:
|
||||
// 1. Only one query-and-report-to-listener operation at a time.
|
||||
// 2. At least one query-and-report-to-listener AFTER every PackageManager state change.
|
||||
if (queryResultFuture == null) {
|
||||
queryResultFuture = Futures.submit(this::queryPackageManager, sequentialExecutor);
|
||||
queryResultFuture.addListener(this::onQueryComplete, syncContext);
|
||||
} else {
|
||||
// There's already a query in-flight but (2) says we need at least one more. Our sequential
|
||||
// Executor would be enough to ensure (1) but we also don't want a backlog of work to build up
|
||||
// if things change rapidly. Just make a note to start a new query when this one finishes.
|
||||
queryNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void onQueryComplete() {
|
||||
syncContext.throwIfNotInThisSynchronizationContext();
|
||||
checkState(queryResultFuture != null);
|
||||
checkState(queryResultFuture.isDone());
|
||||
|
||||
// Capture non-final `listener` here while we're on 'syncContext'.
|
||||
Listener2 listener = checkNotNull(this.listener);
|
||||
Futures.addCallback(
|
||||
queryResultFuture, // Already isDone() so this execute()s immediately.
|
||||
new FutureCallback<ResolutionResult>() {
|
||||
@Override
|
||||
public void onSuccess(ResolutionResult result) {
|
||||
listener.onResult2(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
listener.onResult2(
|
||||
ResolutionResult.newBuilder()
|
||||
.setAddressesOrError(StatusOr.fromStatus(Status.fromThrowable(t)))
|
||||
.build());
|
||||
}
|
||||
},
|
||||
syncContext); // Already on 'syncContext' but addCallback() is faster than try/get/catch.
|
||||
queryResultFuture = null;
|
||||
|
||||
if (queryNeeded) {
|
||||
// One or more resolve() requests arrived while we were working on the last one. Just one
|
||||
// follow-on query can subsume all of them.
|
||||
queryNeeded = false;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAuthority() {
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
syncContext.throwIfNotInThisSynchronizationContext();
|
||||
if (!shutdown) {
|
||||
shutdown = true;
|
||||
sequentialExecutor.execute(this::maybeUnregisterReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
private ResolutionResult queryPackageManager() throws StatusException {
|
||||
List<ResolveInfo> queryResults = queryIntentServices(targetIntent);
|
||||
|
||||
// Avoid a spurious UnsafeIntentLaunchViolation later. Since S, Android's StrictMode is very
|
||||
// conservative, marking any Intent parsed from a string as suspicious and complaining when you
|
||||
// bind to it. But all this is pointless with grpc-binder, which already goes even further by
|
||||
// not trusting addresses at all! Instead, we rely on SecurityPolicy, which won't allow a
|
||||
// connection to an unauthorized server UID no matter how you got there.
|
||||
Intent prototypeBindIntent = sanitize(targetIntent);
|
||||
|
||||
// Model each matching android.app.Service as an EAG (server) with a single address.
|
||||
List<EquivalentAddressGroup> addresses = new ArrayList<>();
|
||||
for (ResolveInfo resolveInfo : queryResults) {
|
||||
prototypeBindIntent.setComponent(
|
||||
new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name));
|
||||
addresses.add(
|
||||
new EquivalentAddressGroup(
|
||||
AndroidComponentAddress.newBuilder()
|
||||
.setBindIntent(prototypeBindIntent) // Makes a copy.
|
||||
.setTargetUser(targetUser)
|
||||
.build(),
|
||||
CONSTANT_EAG_ATTRS));
|
||||
}
|
||||
|
||||
return ResolutionResult.newBuilder()
|
||||
.setAddressesOrError(StatusOr.fromValue(addresses))
|
||||
// Empty service config means we get the default 'pick_first' load balancing policy.
|
||||
.setServiceConfig(serviceConfigParser.parseServiceConfig(ImmutableMap.of()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private List<ResolveInfo> queryIntentServices(Intent intent) throws StatusException {
|
||||
int flags = 0;
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
// Don't match direct-boot-unaware Services that can't presently be created. We'll query again
|
||||
// after the user is unlocked. The MATCH_DIRECT_BOOT_AUTO behavior is actually the default but
|
||||
// being explicit here avoids an android.os.strictmode.ImplicitDirectBootViolation.
|
||||
flags |= PackageManager.MATCH_DIRECT_BOOT_AUTO;
|
||||
}
|
||||
|
||||
List<ResolveInfo> intentServices =
|
||||
targetUserContext.getPackageManager().queryIntentServices(intent, flags);
|
||||
if (intentServices == null || intentServices.isEmpty()) {
|
||||
// Must be the same as when ServiceBinding's call to bindService() returns false.
|
||||
throw Status.UNIMPLEMENTED
|
||||
.withDescription("Service not found for intent " + intent)
|
||||
.asException();
|
||||
}
|
||||
return intentServices;
|
||||
}
|
||||
|
||||
// Returns a new Intent with the same action, data and categories as 'input'.
|
||||
private static Intent sanitize(Intent input) {
|
||||
Intent output = new Intent();
|
||||
output.setAction(input.getAction());
|
||||
output.setData(input.getData());
|
||||
|
||||
Set<String> categories = input.getCategories();
|
||||
if (categories != null) {
|
||||
for (String category : categories) {
|
||||
output.addCategory(category);
|
||||
}
|
||||
}
|
||||
// Don't bother copying extras and flags since AndroidComponentAddress (rightly) ignores them.
|
||||
// Don't bother copying package or ComponentName either, since we're about to set that.
|
||||
return output;
|
||||
}
|
||||
|
||||
final class PackageChangeReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// Get off the main thread and into the correct SynchronizationContext.
|
||||
syncContext.executeLater(IntentNameResolver.this::resolve);
|
||||
offloadExecutor.execute(syncContext::drain);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnprotectedReceiver") // All of these are protected system broadcasts.
|
||||
private void registerReceiver() {
|
||||
checkState(receiver == null, "Already registered!");
|
||||
receiver = new PackageChangeReceiver();
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addDataScheme("package");
|
||||
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||
|
||||
targetUserContext.registerReceiver(receiver, filter);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
// Clients running in direct boot mode must refresh() when the user is unlocked because
|
||||
// that's when `directBootAware=false` services become visible in queryIntentServices()
|
||||
// results. ACTION_BOOT_COMPLETED would work too but it's delivered with lower priority.
|
||||
targetUserContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUnregisterReceiver() {
|
||||
if (receiver != null) { // NameResolver API contract appears to allow shutdown without start().
|
||||
targetUserContext.unregisterReceiver(receiver);
|
||||
receiver = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static android.content.Intent.URI_INTENT_SCHEME;
|
||||
|
||||
import android.content.Intent;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.Args;
|
||||
import io.grpc.NameResolverProvider;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link NameResolverProvider} that handles Android-standard "intent:" target URIs, resolving
|
||||
* them to the list of {@link AndroidComponentAddress} that match by manifest intent filter.
|
||||
*/
|
||||
public final class IntentNameResolverProvider extends NameResolverProvider {
|
||||
|
||||
static final String ANDROID_INTENT_SCHEME = "intent";
|
||||
|
||||
@Override
|
||||
public String getDefaultScheme() {
|
||||
return ANDROID_INTENT_SCHEME;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public NameResolver newNameResolver(URI targetUri, final Args args) {
|
||||
if (Objects.equals(targetUri.getScheme(), ANDROID_INTENT_SCHEME)) {
|
||||
return new IntentNameResolver(parseUriArg(targetUri), args);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 3; // Lower than DNS so we don't accidentally become the default scheme for a registry.
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableSet<Class<? extends SocketAddress>> getProducedSocketAddressTypes() {
|
||||
return ImmutableSet.of(AndroidComponentAddress.class);
|
||||
}
|
||||
|
||||
private static Intent parseUriArg(URI targetUri) {
|
||||
try {
|
||||
return Intent.parseUri(targetUri.toString(), URI_INTENT_SCHEME);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -193,18 +193,27 @@ final class ServiceBinding implements Bindable, ServiceConnection {
|
|||
bindResult = context.bindService(bindIntent, conn, flags);
|
||||
break;
|
||||
case BIND_SERVICE_AS_USER:
|
||||
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
|
||||
} else {
|
||||
return Status.INTERNAL.withDescription("Cross user Channel requires Android R+");
|
||||
}
|
||||
break;
|
||||
case DEVICE_POLICY_BIND_SEVICE_ADMIN:
|
||||
DevicePolicyManager devicePolicyManager =
|
||||
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
bindResult =
|
||||
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
||||
channelCredentials.getDevicePolicyAdminComponentName(),
|
||||
bindIntent,
|
||||
conn,
|
||||
flags,
|
||||
targetUserHandle);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
bindResult =
|
||||
devicePolicyManager.bindDeviceAdminServiceAsUser(
|
||||
channelCredentials.getDevicePolicyAdminComponentName(),
|
||||
bindIntent,
|
||||
conn,
|
||||
flags,
|
||||
targetUserHandle);
|
||||
} else {
|
||||
return Status.INTERNAL.withDescription(
|
||||
"Device policy admin binding requires Android R+");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!bindResult) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
BinderTransport.BinderServerTransport transport;
|
||||
BinderServerTransport transport;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
transport =
|
||||
new BinderTransport.BinderServerTransport(
|
||||
new BinderServerTransport(
|
||||
new FixedObjectPool<>(executorService),
|
||||
Attributes.EMPTY,
|
||||
ImmutableList.of(),
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Application;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.ResolutionResult;
|
||||
import io.grpc.NameResolver.ServiceConfigParser;
|
||||
import io.grpc.NameResolverProvider;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.binder.ApiConstants;
|
||||
import java.net.URI;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoTestRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** A test for IntentNameResolverProvider. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class IntentNameResolverProviderTest {
|
||||
|
||||
private final Application appContext = ApplicationProvider.getApplicationContext();
|
||||
private final SynchronizationContext syncContext = newSynchronizationContext();
|
||||
private final NameResolver.Args args = newNameResolverArgs();
|
||||
|
||||
private NameResolverProvider provider;
|
||||
|
||||
@Rule public MockitoTestRule mockitoTestRule = MockitoJUnit.testRule(this);
|
||||
@Mock public NameResolver.Listener2 mockListener;
|
||||
@Captor public ArgumentCaptor<ResolutionResult> resultCaptor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
provider = new IntentNameResolverProvider();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProviderScheme_returnsIntentScheme() throws Exception {
|
||||
assertThat(provider.getDefaultScheme())
|
||||
.isEqualTo(IntentNameResolverProvider.ANDROID_INTENT_SCHEME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoResolverForUnknownScheme_returnsNull() throws Exception {
|
||||
assertThat(provider.newNameResolver(new URI("random://uri"), args)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionWithBadUri_throwsIllegalArg() throws Exception {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> provider.newNameResolver(new URI("intent:xxx#Intent;e.x=1;end;"), args));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolverForIntentScheme_returnsResolver() throws Exception {
|
||||
URI uri = new URI("intent://authority/path#Intent;action=action;scheme=scheme;end");
|
||||
NameResolver resolver = provider.newNameResolver(uri, args);
|
||||
assertThat(resolver).isNotNull();
|
||||
assertThat(resolver.getServiceAuthority()).isEqualTo("localhost");
|
||||
syncContext.execute(() -> resolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(resultCaptor.getValue().getAddressesOrError()).isNotNull();
|
||||
syncContext.execute(resolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
/** Returns a new test-specific {@link NameResolver.Args} instance. */
|
||||
private NameResolver.Args newNameResolverArgs() {
|
||||
return NameResolver.Args.newBuilder()
|
||||
.setDefaultPort(-1)
|
||||
.setProxyDetector((target) -> null) // No proxies here.
|
||||
.setSynchronizationContext(syncContext)
|
||||
.setOffloadExecutor(ContextCompat.getMainExecutor(appContext))
|
||||
.setServiceConfigParser(mock(ServiceConfigParser.class))
|
||||
.setArg(ApiConstants.SOURCE_ANDROID_CONTEXT, appContext)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static SynchronizationContext newSynchronizationContext() {
|
||||
return new SynchronizationContext(
|
||||
(thread, exception) -> {
|
||||
throw new AssertionError(exception);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* Copyright 2025 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.grpc.binder.internal;
|
||||
|
||||
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_REPLACED;
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static android.os.Process.myUserHandle;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.grpc.EquivalentAddressGroup;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.ResolutionResult;
|
||||
import io.grpc.NameResolver.ServiceConfigParser;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusOr;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.binder.AndroidComponentAddress;
|
||||
import io.grpc.binder.ApiConstants;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoTestRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
|
||||
/** A test for IntentNameResolverProvider. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class IntentNameResolverTest {
|
||||
|
||||
private static final ComponentName SOME_COMPONENT_NAME =
|
||||
new ComponentName("com.foo.bar", "SomeComponent");
|
||||
private static final ComponentName ANOTHER_COMPONENT_NAME =
|
||||
new ComponentName("org.blah", "AnotherComponent");
|
||||
private final Application appContext = ApplicationProvider.getApplicationContext();
|
||||
private final SynchronizationContext syncContext = newSynchronizationContext();
|
||||
private final NameResolver.Args args = newNameResolverArgs().build();
|
||||
|
||||
private final ShadowPackageManager shadowPackageManager =
|
||||
shadowOf(appContext.getPackageManager());
|
||||
|
||||
@Rule public MockitoTestRule mockitoTestRule = MockitoJUnit.testRule(this);
|
||||
@Mock public NameResolver.Listener2 mockListener;
|
||||
@Captor public ArgumentCaptor<ResolutionResult> resultCaptor;
|
||||
|
||||
@Test
|
||||
public void testResolverForIntentScheme_returnsResolverWithLocalHostAuthority() throws Exception {
|
||||
NameResolver resolver = newNameResolver(newIntent());
|
||||
assertThat(resolver).isNotNull();
|
||||
assertThat(resolver.getServiceAuthority()).isEqualTo("localhost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionWithoutServicesAvailable_returnsUnimplemented() throws Exception {
|
||||
NameResolver nameResolver = newNameResolver(newIntent());
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(resultCaptor.getValue().getAddressesOrError().getStatus().getCode())
|
||||
.isEqualTo(Status.UNIMPLEMENTED.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionWithMultipleServicesAvailable_returnsAndroidComponentAddresses()
|
||||
throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
// Adds another valid Service
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitResolutionByComponent_returnsRestrictedResults() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver =
|
||||
newNameResolver(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME));
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitResolutionByPackage_returnsRestrictedResults() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver =
|
||||
newNameResolver(intent.cloneFilter().setPackage(ANOTHER_COMPONENT_NAME.getPackageName()));
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolution_setsPreAuthEagAttribute() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||
assertThat(
|
||||
getEagsOrThrow(resultCaptor.getValue()).stream()
|
||||
.map(EquivalentAddressGroup::getAttributes)
|
||||
.collect(toImmutableList())
|
||||
.get(0)
|
||||
.get(ApiConstants.PRE_AUTH_SERVER_OVERRIDE))
|
||||
.isTrue();
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceRemoved_pushesUpdatedAndroidComponentAddresses() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
shadowPackageManager.removeService(ANOTHER_COMPONENT_NAME);
|
||||
broadcastPackageChange(ACTION_PACKAGE_REPLACED, ANOTHER_COMPONENT_NAME.getPackageName());
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verifyNoMoreInteractions(mockListener);
|
||||
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 30)
|
||||
public void testTargetAndroidUser_pushesUpdatedAddresses() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
NameResolver nameResolver =
|
||||
newNameResolver(
|
||||
intent,
|
||||
newNameResolverArgs().setArg(ApiConstants.TARGET_ANDROID_USER, myUserHandle()).build());
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(resultCaptor.getValue().getAddressesOrError().getStatus().getCode())
|
||||
.isEqualTo(Status.UNIMPLEMENTED.getCode());
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
broadcastPackageChange(ACTION_PACKAGE_ADDED, SOME_COMPONENT_NAME.getPackageName());
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
ImmutableList.of(
|
||||
AndroidComponentAddress.newBuilder()
|
||||
.setTargetUser(myUserHandle())
|
||||
.setBindIntent(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME))
|
||||
.build()));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verifyNoMoreInteractions(mockListener);
|
||||
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 29)
|
||||
public void testTargetAndroidUser_notSupported_throwsWithHelpfulMessage() throws Exception {
|
||||
NameResolver.Args args =
|
||||
newNameResolverArgs().setArg(ApiConstants.TARGET_ANDROID_USER, myUserHandle()).build();
|
||||
IllegalArgumentException iae =
|
||||
assertThrows(IllegalArgumentException.class, () -> newNameResolver(newIntent(), args));
|
||||
assertThat(iae.getMessage()).contains("TARGET_ANDROID_USER");
|
||||
assertThat(iae.getMessage()).contains("SDK_INT >= R");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 29)
|
||||
public void testServiceAppearsUponBootComplete_pushesUpdatedAndroidComponentAddresses()
|
||||
throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
// Suppose this directBootAware=true Service appears in PackageManager before a user unlock.
|
||||
shadowOf(appContext.getSystemService(UserManager.class)).setUserUnlocked(false);
|
||||
ServiceInfo someServiceInfo = shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
someServiceInfo.directBootAware = true;
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||
|
||||
// TODO(b/331618070): Robolectric doesn't yet support ServiceInfo.directBootAware filtering.
|
||||
// Simulate support by waiting for a user unlock to add this !directBootAware Service.
|
||||
ServiceInfo anotherServiceInfo =
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
anotherServiceInfo.directBootAware = false;
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
shadowOf(appContext.getSystemService(UserManager.class)).setUserUnlocked(true);
|
||||
broadcastUserUnlocked(myUserHandle());
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verifyNoMoreInteractions(mockListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh_returnsSameAndroidComponentAddresses() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
shadowPackageManager.addServiceIfNotPresent(ANOTHER_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(ANOTHER_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::refresh);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(
|
||||
toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)),
|
||||
toAddressList(intent.cloneFilter().setComponent(ANOTHER_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
assertThat(shadowOf(appContext).getRegisteredReceivers()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh_collapsesMultipleRequestsIntoOneLookup() throws Exception {
|
||||
Intent intent = newIntent();
|
||||
IntentFilter serviceIntentFilter = newFilterMatching(intent);
|
||||
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, serviceIntentFilter);
|
||||
|
||||
NameResolver nameResolver = newNameResolver(intent);
|
||||
syncContext.execute(() -> nameResolver.start(mockListener)); // Should kick off the 1st lookup.
|
||||
syncContext.execute(nameResolver::refresh); // Should queue a lookup to run when 1st finishes.
|
||||
syncContext.execute(nameResolver::refresh); // Should be ignored since a lookup is already Q'd.
|
||||
syncContext.execute(nameResolver::refresh); // Also ignored.
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener, never()).onError(any());
|
||||
verify(mockListener, times(2)).onResult2(resultCaptor.capture());
|
||||
assertThat(getAddressesOrThrow(resultCaptor.getValue()))
|
||||
.containsExactly(toAddressList(intent.cloneFilter().setComponent(SOME_COMPONENT_NAME)));
|
||||
|
||||
syncContext.execute(nameResolver::shutdown);
|
||||
shadowOf(getMainLooper()).idle();
|
||||
}
|
||||
|
||||
private void broadcastPackageChange(String action, String pkgName) {
|
||||
Intent broadcast = new Intent();
|
||||
broadcast.setAction(action);
|
||||
broadcast.setData(Uri.parse("package:" + pkgName));
|
||||
appContext.sendBroadcast(broadcast);
|
||||
}
|
||||
|
||||
private void broadcastUserUnlocked(UserHandle userHandle) {
|
||||
Intent unlockedBroadcast = new Intent(Intent.ACTION_USER_UNLOCKED);
|
||||
unlockedBroadcast.putExtra(Intent.EXTRA_USER, userHandle);
|
||||
appContext.sendBroadcast(unlockedBroadcast);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionOnResultThrows_onErrorNotCalled() throws Exception {
|
||||
RetainingUncaughtExceptionHandler exceptionHandler = new RetainingUncaughtExceptionHandler();
|
||||
SynchronizationContext syncContext = new SynchronizationContext(exceptionHandler);
|
||||
Intent intent = newIntent();
|
||||
shadowPackageManager.addServiceIfNotPresent(SOME_COMPONENT_NAME);
|
||||
shadowPackageManager.addIntentFilterForService(SOME_COMPONENT_NAME, newFilterMatching(intent));
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
class SomeRuntimeException extends RuntimeException {}
|
||||
doThrow(SomeRuntimeException.class).when(mockListener).onResult2(any());
|
||||
|
||||
NameResolver nameResolver =
|
||||
newNameResolver(
|
||||
intent, newNameResolverArgs().setSynchronizationContext(syncContext).build());
|
||||
syncContext.execute(() -> nameResolver.start(mockListener));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
verify(mockListener).onResult2(any());
|
||||
verify(mockListener, never()).onError(any());
|
||||
assertThat(exceptionHandler.uncaught).hasSize(1);
|
||||
assertThat(exceptionHandler.uncaught.get(0)).isInstanceOf(SomeRuntimeException.class);
|
||||
}
|
||||
|
||||
private static Intent newIntent() {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction("test.action");
|
||||
intent.setData(Uri.parse("grpc:ServiceName"));
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static IntentFilter newFilterMatching(Intent intent) {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
if (intent.getAction() != null) {
|
||||
filter.addAction(intent.getAction());
|
||||
}
|
||||
Uri data = intent.getData();
|
||||
if (data != null) {
|
||||
if (data.getScheme() != null) {
|
||||
filter.addDataScheme(data.getScheme());
|
||||
}
|
||||
if (data.getSchemeSpecificPart() != null) {
|
||||
filter.addDataSchemeSpecificPart(data.getSchemeSpecificPart(), 0);
|
||||
}
|
||||
}
|
||||
Set<String> categories = intent.getCategories();
|
||||
if (categories != null) {
|
||||
for (String category : categories) {
|
||||
filter.addCategory(category);
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static List<EquivalentAddressGroup> getEagsOrThrow(ResolutionResult result) {
|
||||
StatusOr<List<EquivalentAddressGroup>> eags = result.getAddressesOrError();
|
||||
if (!eags.hasValue()) {
|
||||
throw eags.getStatus().asRuntimeException();
|
||||
}
|
||||
return eags.getValue();
|
||||
}
|
||||
|
||||
// Extracts just the addresses from 'result's EquivalentAddressGroups.
|
||||
private static ImmutableList<List<SocketAddress>> getAddressesOrThrow(ResolutionResult result) {
|
||||
return getEagsOrThrow(result).stream()
|
||||
.map(EquivalentAddressGroup::getAddresses)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
// Converts given Intents to a list of ACAs, for convenient comparison with getAddressesOrThrow().
|
||||
private static ImmutableList<AndroidComponentAddress> toAddressList(Intent... bindIntents) {
|
||||
ImmutableList.Builder<AndroidComponentAddress> builder = ImmutableList.builder();
|
||||
for (Intent bindIntent : bindIntents) {
|
||||
builder.add(AndroidComponentAddress.forBindIntent(bindIntent));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private NameResolver newNameResolver(Intent targetIntent) {
|
||||
return newNameResolver(targetIntent, args);
|
||||
}
|
||||
|
||||
private NameResolver newNameResolver(Intent targetIntent, NameResolver.Args args) {
|
||||
return new IntentNameResolver(targetIntent, args);
|
||||
}
|
||||
|
||||
/** Returns a new test-specific {@link NameResolver.Args} instance. */
|
||||
private NameResolver.Args.Builder newNameResolverArgs() {
|
||||
return NameResolver.Args.newBuilder()
|
||||
.setDefaultPort(-1)
|
||||
.setProxyDetector((target) -> null) // No proxies here.
|
||||
.setSynchronizationContext(syncContext)
|
||||
.setOffloadExecutor(ContextCompat.getMainExecutor(appContext))
|
||||
.setArg(ApiConstants.SOURCE_ANDROID_CONTEXT, appContext)
|
||||
.setServiceConfigParser(mock(ServiceConfigParser.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a test {@link SynchronizationContext}.
|
||||
*
|
||||
* <p>Exceptions will cause the test to fail with {@link AssertionError}.
|
||||
*/
|
||||
private static SynchronizationContext newSynchronizationContext() {
|
||||
return new SynchronizationContext(
|
||||
(thread, exception) -> {
|
||||
throw new AssertionError(exception);
|
||||
});
|
||||
}
|
||||
|
||||
static final class RetainingUncaughtExceptionHandler implements UncaughtExceptionHandler {
|
||||
final ArrayList<Throwable> uncaught = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
|
||||
uncaught.add(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import android.content.ComponentName;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.UserHandle;
|
||||
|
@ -327,6 +328,50 @@ public final class ServiceBindingTest {
|
|||
assertThat(statusException.getStatus().getDescription()).contains("12345");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 30)
|
||||
public void testBindService_doesNotThrowInternalErrorWhenSdkAtLeastR() {
|
||||
UserHandle userHandle = generateUserHandle(/* userId= */ 12345);
|
||||
binding = newBuilder().setTargetUserHandle(userHandle).build();
|
||||
binding.bind();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertThat(Build.VERSION.SDK_INT).isEqualTo(Build.VERSION_CODES.R);
|
||||
assertThat(observer.unboundReason).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 28)
|
||||
public void testBindServiceAsUser_returnsErrorWhenSdkBelowR() {
|
||||
UserHandle userHandle = generateUserHandle(/* userId= */ 12345);
|
||||
binding = newBuilder().setTargetUserHandle(userHandle).build();
|
||||
binding.bind();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.INTERNAL);
|
||||
assertThat(observer.unboundReason.getDescription())
|
||||
.isEqualTo("Cross user Channel requires Android R+");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 28)
|
||||
public void testDevicePolicyBlind_returnsErrorWhenSdkBelowR() {
|
||||
String deviceAdminClassName = "DevicePolicyAdmin";
|
||||
ComponentName adminComponent = new ComponentName(appContext, deviceAdminClassName);
|
||||
allowBindDeviceAdminForUser(appContext, adminComponent, 10);
|
||||
binding =
|
||||
newBuilder()
|
||||
.setTargetUserHandle(UserHandle.getUserHandleForUid(10))
|
||||
.setChannelCredentials(BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
|
||||
.build();
|
||||
binding.bind();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertThat(observer.unboundReason.getCode()).isEqualTo(Code.INTERNAL);
|
||||
assertThat(observer.unboundReason.getDescription())
|
||||
.isEqualTo("Device policy admin binding requires Android R+");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = 30)
|
||||
public void testBindWithDeviceAdmin() throws Exception {
|
||||
|
|
|
@ -24,8 +24,8 @@ import io.grpc.internal.TestUtils.NoopChannelLogger;
|
|||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* Helps unit tests create {@link BinderTransport.BinderClientTransport} instances without having to
|
||||
* mention irrelevant details (go/tott/719).
|
||||
* Helps unit tests create {@link BinderClientTransport} instances without having to mention
|
||||
* irrelevant details (go/tott/719).
|
||||
*/
|
||||
public class BinderClientTransportBuilder {
|
||||
private BinderClientTransportFactory factory;
|
||||
|
@ -54,7 +54,7 @@ public class BinderClientTransportBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public BinderTransport.BinderClientTransport build() {
|
||||
public BinderClientTransport build() {
|
||||
return factory.newClientTransport(
|
||||
checkNotNull(serverAddress), checkNotNull(options), checkNotNull(channelLogger));
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ subprojects {
|
|||
apply plugin: "net.ltgt.errorprone"
|
||||
|
||||
group = "io.grpc"
|
||||
version = "1.75.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
||||
version = "1.76.0-SNAPSHOT" // CURRENT_GRPC_VERSION
|
||||
|
||||
repositories {
|
||||
maven { // The google mirror is less flaky than mavenCentral()
|
||||
|
|
|
@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* </pre>
|
||||
*/
|
||||
@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")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
@java.lang.Deprecated
|
||||
|
|
|
@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
|
|||
* </pre>
|
||||
*/
|
||||
@javax.annotation.Generated(
|
||||
value = "by gRPC proto compiler (version 1.75.0-SNAPSHOT)",
|
||||
value = "by gRPC proto compiler (version 1.76.0-SNAPSHOT)",
|
||||
comments = "Source: grpc/testing/compiler/test.proto")
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class TestServiceGrpc {
|
||||
|
|
|
@ -219,7 +219,7 @@ public final class GrpcUtil {
|
|||
|
||||
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
|
||||
|
||||
public static final String IMPLEMENTATION_VERSION = "1.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.
|
||||
|
|
|
@ -14,7 +14,7 @@ android {
|
|||
namespace = 'io.grpc.cronet'
|
||||
compileSdkVersion 33
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5")
|
||||
bazel_dep(name = "grpc-java", repo_name = "io_grpc_grpc_java", version = "1.75.0-SNAPSHOT") # CURRENT_GRPC_VERSION
|
||||
bazel_dep(name = "grpc-java", repo_name = "io_grpc_grpc_java", version = "1.76.0-SNAPSHOT") # CURRENT_GRPC_VERSION
|
||||
bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58")
|
||||
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
|
||||
bazel_dep(name = "rules_jvm_external", version = "6.0")
|
||||
|
|
|
@ -34,7 +34,7 @@ android {
|
|||
protobuf {
|
||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||
plugins {
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -54,12 +54,12 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
|
||||
// You need to build grpc-java to obtain these libraries below.
|
||||
implementation 'io.grpc:grpc-okhttp:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub: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.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'com.google.truth:truth:1.1.5'
|
||||
testImplementation 'io.grpc:grpc-testing:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
testImplementation 'io.grpc:grpc-testing:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ android {
|
|||
protobuf {
|
||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||
plugins {
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -52,8 +52,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
|
||||
// You need to build grpc-java to obtain these libraries below.
|
||||
implementation 'io.grpc:grpc-okhttp:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub: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.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ android {
|
|||
protobuf {
|
||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||
plugins {
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -52,8 +52,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
|
||||
// You need to build grpc-java to obtain these libraries below.
|
||||
implementation 'io.grpc:grpc-okhttp:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub: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.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ android {
|
|||
protobuf {
|
||||
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
|
||||
plugins {
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
|
@ -53,8 +53,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
|
||||
// You need to build grpc-java to obtain these libraries below.
|
||||
implementation 'io.grpc:grpc-okhttp:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-protobuf-lite:1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub: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.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
implementation 'org.apache.tomcat:annotations-api:6.0.53'
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
def protocVersion = protobufVersion
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
// grpc-alts transitively depends on grpc-netty-shaded, grpc-protobuf, and grpc-stub
|
||||
|
|
|
@ -23,8 +23,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-debug</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -23,8 +23,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-dualstack</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
def protocVersion = protobufVersion
|
||||
|
||||
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-gauth</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.8</protobuf.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -22,10 +22,10 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def openTelemetryVersion = '1.40.0'
|
||||
def openTelemetryPrometheusVersion = '1.40.0-alpha'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
def openTelemetryVersion = '1.52.0'
|
||||
def openTelemetryPrometheusVersion = '1.52.0-alpha'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -22,8 +22,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-hostname</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
def protocVersion = protobufVersion
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-jwt-auth</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.8</protobuf.version>
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protobufVersion = '3.25.8'
|
||||
def protocVersion = protobufVersion
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-oauth</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.8</protobuf.version>
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,10 +21,10 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def openTelemetryVersion = '1.40.0'
|
||||
def openTelemetryPrometheusVersion = '1.40.0-alpha'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
def openTelemetryVersion = '1.52.0'
|
||||
def openTelemetryPrometheusVersion = '1.52.0-alpha'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -16,8 +16,8 @@ java {
|
|||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -16,8 +16,8 @@ java {
|
|||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -15,8 +15,8 @@ java {
|
|||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}",
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>example-tls</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for jdk9 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -21,8 +21,8 @@ java {
|
|||
|
||||
// Feel free to delete the comment at the next line. It is just for safely
|
||||
// updating the version in our release process.
|
||||
def grpcVersion = '1.75.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.5'
|
||||
def grpcVersion = '1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||
def protocVersion = '3.25.8'
|
||||
|
||||
dependencies {
|
||||
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
<packaging>jar</packaging>
|
||||
<!-- Feel free to delete the comment at the end of these lines. It is just
|
||||
for safely updating the version in our release process. -->
|
||||
<version>1.75.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<version>1.76.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
|
||||
<name>examples</name>
|
||||
<url>https://github.com/grpc/grpc-java</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<grpc.version>1.75.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<protoc.version>3.25.5</protoc.version>
|
||||
<grpc.version>1.76.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
|
||||
<protobuf.version>3.25.8</protobuf.version>
|
||||
<protoc.version>3.25.8</protoc.version>
|
||||
<!-- required for JDK 8 -->
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
[versions]
|
||||
netty = '4.1.110.Final'
|
||||
netty = '4.1.124.Final'
|
||||
# Keep the following references of tcnative version in sync whenever it's updated:
|
||||
# SECURITY.md
|
||||
nettytcnative = '2.0.70.Final'
|
||||
nettytcnative = '2.0.72.Final'
|
||||
opencensus = "0.31.1"
|
||||
# Not upgrading to 4.x as it is not yet ABI compatible.
|
||||
# https://github.com/protocolbuffers/protobuf/issues/17247
|
||||
protobuf = "3.25.5"
|
||||
protobuf = "3.25.8"
|
||||
|
||||
[libraries]
|
||||
android-annotations = "com.google.android:annotations:4.1.1.4"
|
||||
# androidx-annotation 1.9.1+ uses Kotlin and requires Android Gradle Plugin 9+
|
||||
androidx-annotation = "androidx.annotation:annotation:1.9.0"
|
||||
# 1.15.0 requires libraries and applications that depend on it to compile against
|
||||
# version 35 or later of the Android APIs.
|
||||
androidx-core = "androidx.core:core:1.13.1"
|
||||
# androidx-lifecycle 2.9+ requires Java 17
|
||||
androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.8.7"
|
||||
androidx-lifecycle-service = "androidx.lifecycle:lifecycle-service:2.8.7"
|
||||
androidx-test-core = "androidx.test:core:1.6.1"
|
||||
|
@ -36,13 +38,13 @@ cronet-embedded = "org.chromium.net:cronet-embedded:119.6045.31"
|
|||
errorprone-annotations = "com.google.errorprone:error_prone_annotations:2.30.0"
|
||||
# error-prone 2.32.0+ require Java 17+
|
||||
errorprone-core = "com.google.errorprone:error_prone_core:2.31.0"
|
||||
google-api-protos = "com.google.api.grpc:proto-google-common-protos:2.51.0"
|
||||
google-api-protos = "com.google.api.grpc:proto-google-common-protos:2.59.2"
|
||||
# google-auth-library 1.25.0+ requires error_prone_annotations 2.31.0+, which
|
||||
# breaks the Android build
|
||||
google-auth-credentials = "com.google.auth:google-auth-library-credentials:1.24.1"
|
||||
google-auth-oauth2Http = "com.google.auth:google-auth-library-oauth2-http:1.24.1"
|
||||
# Release notes: https://cloud.google.com/logging/docs/release-notes
|
||||
google-cloud-logging = "com.google.cloud:google-cloud-logging:3.21.2"
|
||||
google-cloud-logging = "com.google.cloud:google-cloud-logging:3.23.1"
|
||||
# 2.12.1 requires error_prone_annotations:2.36.0 but we are stuck with 2.30.0
|
||||
gson = "com.google.code.gson:gson:2.11.0"
|
||||
# 33.4.0 requires com.google.errorprone:error_prone_annotations:2.36.0 but we are stuck with 2.30.0 (see above)
|
||||
|
@ -61,7 +63,7 @@ javax-annotation = "org.apache.tomcat:annotations-api:6.0.53"
|
|||
javax-servlet-api = "javax.servlet:javax.servlet-api:4.0.1"
|
||||
# 12.0.0+ require Java 17+
|
||||
jetty-client = "org.eclipse.jetty:jetty-client:11.0.24"
|
||||
jetty-http2-server = "org.eclipse.jetty.http2:jetty-http2-server:12.0.16"
|
||||
jetty-http2-server = "org.eclipse.jetty.http2:jetty-http2-server:12.0.23"
|
||||
jetty-http2-server10 = "org.eclipse.jetty.http2:http2-server:10.0.20"
|
||||
jetty-servlet = "org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.16"
|
||||
jetty-servlet10 = "org.eclipse.jetty:jetty-servlet:10.0.20"
|
||||
|
@ -91,19 +93,19 @@ opencensus-contrib-grpc-metrics = { module = "io.opencensus:opencensus-contrib-g
|
|||
opencensus-exporter-stats-stackdriver = { module = "io.opencensus:opencensus-exporter-stats-stackdriver", version.ref = "opencensus" }
|
||||
opencensus-exporter-trace-stackdriver = { module = "io.opencensus:opencensus-exporter-trace-stackdriver", version.ref = "opencensus" }
|
||||
opencensus-impl = { module = "io.opencensus:opencensus-impl", version.ref = "opencensus" }
|
||||
opentelemetry-api = "io.opentelemetry:opentelemetry-api:1.46.0"
|
||||
opentelemetry-exporter-prometheus = "io.opentelemetry:opentelemetry-exporter-prometheus:1.46.0-alpha"
|
||||
opentelemetry-gcp-resources = "io.opentelemetry.contrib:opentelemetry-gcp-resources:1.43.0-alpha"
|
||||
opentelemetry-sdk-extension-autoconfigure = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.46.0"
|
||||
opentelemetry-sdk-testing = "io.opentelemetry:opentelemetry-sdk-testing:1.46.0"
|
||||
opentelemetry-api = "io.opentelemetry:opentelemetry-api:1.52.0"
|
||||
opentelemetry-exporter-prometheus = "io.opentelemetry:opentelemetry-exporter-prometheus:1.52.0-alpha"
|
||||
opentelemetry-gcp-resources = "io.opentelemetry.contrib:opentelemetry-gcp-resources:1.48.0-alpha"
|
||||
opentelemetry-sdk-extension-autoconfigure = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.52.0"
|
||||
opentelemetry-sdk-testing = "io.opentelemetry:opentelemetry-sdk-testing:1.52.0"
|
||||
perfmark-api = "io.perfmark:perfmark-api:0.27.0"
|
||||
protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
|
||||
protobuf-java-util = { module = "com.google.protobuf:protobuf-java-util", version.ref = "protobuf" }
|
||||
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
|
||||
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
|
||||
re2j = "com.google.re2j:re2j:1.8"
|
||||
robolectric = "org.robolectric:robolectric:4.14.1"
|
||||
s2a-proto = "com.google.s2a.proto.v2:s2a-proto:0.1.1"
|
||||
robolectric = "org.robolectric:robolectric:4.15.1"
|
||||
s2a-proto = "com.google.s2a.proto.v2:s2a-proto:0.1.2"
|
||||
signature-android = "net.sf.androidscents.signature:android-api-level-21:5.0.1_r2"
|
||||
signature-java = "org.codehaus.mojo.signature:java18:1.0"
|
||||
# 11.0.0+ require Java 17+
|
||||
|
|
|
@ -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 IBM_PROVIDER_NAME = "IBMJSSE2";
|
||||
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.
|
||||
|
@ -199,7 +200,8 @@ public class GrpcSslContexts {
|
|||
jdkProvider.getName() + " selected, but Java 9+ and Jetty NPN/ALPN unavailable");
|
||||
}
|
||||
} 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()) {
|
||||
apc = ALPN;
|
||||
} else {
|
||||
|
@ -255,7 +257,8 @@ public class GrpcSslContexts {
|
|||
return provider;
|
||||
}
|
||||
} 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()) {
|
||||
return provider;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ import io.netty.channel.ChannelFuture;
|
|||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
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.DefaultHttp2Connection;
|
||||
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.Http2HeadersDecoder;
|
||||
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.Http2Settings;
|
||||
import io.netty.handler.codec.http2.Http2Stream;
|
||||
|
@ -125,13 +127,11 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
private final long keepAliveTimeoutInNanos;
|
||||
private final long maxConnectionAgeInNanos;
|
||||
private final long maxConnectionAgeGraceInNanos;
|
||||
private final int maxRstCount;
|
||||
private final long maxRstPeriodNanos;
|
||||
private final RstStreamCounter rstStreamCounter;
|
||||
private final List<? extends ServerStreamTracer.Factory> streamTracerFactories;
|
||||
private final TransportTracer transportTracer;
|
||||
private final KeepAliveEnforcer keepAliveEnforcer;
|
||||
private final Attributes eagAttributes;
|
||||
private final Ticker ticker;
|
||||
/** Incomplete attributes produced by negotiator. */
|
||||
private Attributes negotiationAttributes;
|
||||
private InternalChannelz.Security securityInfo;
|
||||
|
@ -149,8 +149,6 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
private ScheduledFuture<?> maxConnectionAgeMonitor;
|
||||
@CheckForNull
|
||||
private GracefulShutdown gracefulShutdown;
|
||||
private int rstCount;
|
||||
private long lastRstNanoTime;
|
||||
|
||||
static NettyServerHandler newHandler(
|
||||
ServerTransportListener transportListener,
|
||||
|
@ -251,6 +249,12 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
final KeepAliveEnforcer keepAliveEnforcer = new KeepAliveEnforcer(
|
||||
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.
|
||||
connection.local().flowController(
|
||||
new DefaultHttp2LocalFlowController(connection, DEFAULT_WINDOW_UPDATE_RATIO, true));
|
||||
|
@ -258,6 +262,7 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
Http2ConnectionEncoder encoder =
|
||||
new DefaultHttp2ConnectionEncoder(connection, frameWriter);
|
||||
encoder = new Http2ControlFrameLimitEncoder(encoder, 10000);
|
||||
encoder = new Http2RstCounterEncoder(encoder, rstStreamCounter);
|
||||
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder,
|
||||
frameReader);
|
||||
|
||||
|
@ -266,10 +271,6 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
settings.maxConcurrentStreams(maxStreams);
|
||||
settings.maxHeaderListSize(maxHeaderListSize);
|
||||
|
||||
if (ticker == null) {
|
||||
ticker = Ticker.systemTicker();
|
||||
}
|
||||
|
||||
return new NettyServerHandler(
|
||||
channelUnused,
|
||||
connection,
|
||||
|
@ -286,8 +287,7 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
|
||||
keepAliveEnforcer,
|
||||
autoFlowControl,
|
||||
maxRstCount,
|
||||
maxRstPeriodNanos,
|
||||
rstStreamCounter,
|
||||
eagAttributes, ticker);
|
||||
}
|
||||
|
||||
|
@ -310,8 +310,7 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
long maxConnectionAgeGraceInNanos,
|
||||
final KeepAliveEnforcer keepAliveEnforcer,
|
||||
boolean autoFlowControl,
|
||||
int maxRstCount,
|
||||
long maxRstPeriodNanos,
|
||||
RstStreamCounter rstStreamCounter,
|
||||
Attributes eagAttributes,
|
||||
Ticker ticker) {
|
||||
super(
|
||||
|
@ -363,12 +362,9 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
|
||||
this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
|
||||
this.keepAliveEnforcer = checkNotNull(keepAliveEnforcer, "keepAliveEnforcer");
|
||||
this.maxRstCount = maxRstCount;
|
||||
this.maxRstPeriodNanos = maxRstPeriodNanos;
|
||||
this.rstStreamCounter = rstStreamCounter;
|
||||
this.eagAttributes = checkNotNull(eagAttributes, "eagAttributes");
|
||||
this.ticker = checkNotNull(ticker, "ticker");
|
||||
|
||||
this.lastRstNanoTime = ticker.read();
|
||||
streamKey = encoder.connection().newKey();
|
||||
this.transportListener = checkNotNull(transportListener, "transportListener");
|
||||
this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
|
||||
|
@ -575,24 +571,9 @@ class NettyServerHandler extends AbstractNettyHandler {
|
|||
}
|
||||
|
||||
private void onRstStreamRead(int streamId, long errorCode) throws Http2Exception {
|
||||
if (maxRstCount > 0) {
|
||||
long now = ticker.read();
|
||||
if (now - lastRstNanoTime > maxRstPeriodNanos) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Http2Exception tooManyRstStream = rstStreamCounter.countRstStream();
|
||||
if (tooManyRstStream != null) {
|
||||
throw tooManyRstStream;
|
||||
}
|
||||
|
||||
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 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.TE_HEADER;
|
||||
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 org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -276,7 +275,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHand
|
|||
public void createStreamShouldSucceed() throws Exception {
|
||||
createStream();
|
||||
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
|
||||
|
|
|
@ -1304,6 +1304,8 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase<NettyServerHand
|
|||
}
|
||||
|
||||
private void rapidReset(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))
|
||||
|
@ -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 {
|
||||
Http2Headers headers = new DefaultHttp2Headers()
|
||||
.method(HTTP_METHOD)
|
||||
|
|
|
@ -12,7 +12,7 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
|||
# GRPC_DEPS_START
|
||||
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
||||
"com.google.android:annotations:4.1.1.4",
|
||||
"com.google.api.grpc:proto-google-common-protos:2.51.0",
|
||||
"com.google.api.grpc:proto-google-common-protos:2.59.2",
|
||||
"com.google.auth:google-auth-library-credentials:1.24.1",
|
||||
"com.google.auth:google-auth-library-oauth2-http:1.24.1",
|
||||
"com.google.auto.value:auto-value-annotations:1.11.0",
|
||||
|
@ -23,24 +23,24 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [
|
|||
"com.google.guava:failureaccess:1.0.1",
|
||||
"com.google.guava:guava:33.3.1-android",
|
||||
"com.google.re2j:re2j:1.8",
|
||||
"com.google.s2a.proto.v2:s2a-proto:0.1.1",
|
||||
"com.google.s2a.proto.v2:s2a-proto:0.1.2",
|
||||
"com.google.truth:truth:1.4.2",
|
||||
"com.squareup.okhttp:okhttp:2.7.5",
|
||||
"com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day
|
||||
"io.netty:netty-buffer:4.1.110.Final",
|
||||
"io.netty:netty-codec-http2:4.1.110.Final",
|
||||
"io.netty:netty-codec-http:4.1.110.Final",
|
||||
"io.netty:netty-codec-socks:4.1.110.Final",
|
||||
"io.netty:netty-codec:4.1.110.Final",
|
||||
"io.netty:netty-common:4.1.110.Final",
|
||||
"io.netty:netty-handler-proxy:4.1.110.Final",
|
||||
"io.netty:netty-handler:4.1.110.Final",
|
||||
"io.netty:netty-resolver:4.1.110.Final",
|
||||
"io.netty:netty-buffer:4.1.124.Final",
|
||||
"io.netty:netty-codec-http2:4.1.124.Final",
|
||||
"io.netty:netty-codec-http:4.1.124.Final",
|
||||
"io.netty:netty-codec-socks:4.1.124.Final",
|
||||
"io.netty:netty-codec:4.1.124.Final",
|
||||
"io.netty:netty-common:4.1.124.Final",
|
||||
"io.netty:netty-handler-proxy:4.1.124.Final",
|
||||
"io.netty:netty-handler:4.1.124.Final",
|
||||
"io.netty:netty-resolver:4.1.124.Final",
|
||||
"io.netty:netty-tcnative-boringssl-static:2.0.70.Final",
|
||||
"io.netty:netty-tcnative-classes:2.0.70.Final",
|
||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.110.Final",
|
||||
"io.netty:netty-transport:4.1.110.Final",
|
||||
"io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.124.Final",
|
||||
"io.netty:netty-transport-native-unix-common:4.1.124.Final",
|
||||
"io.netty:netty-transport:4.1.124.Final",
|
||||
"io.opencensus:opencensus-api:0.31.0",
|
||||
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
|
||||
"io.perfmark:perfmark-api:0.27.0",
|
||||
|
@ -116,9 +116,9 @@ def com_google_protobuf():
|
|||
# This statement defines the @com_google_protobuf repo.
|
||||
http_archive(
|
||||
name = "com_google_protobuf",
|
||||
sha256 = "9bd87b8280ef720d3240514f884e56a712f2218f0d693b48050c836028940a42",
|
||||
strip_prefix = "protobuf-25.1",
|
||||
urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v25.1/protobuf-25.1.tar.gz"],
|
||||
sha256 = "3cf7d5b17c4ff04fe9f038104e9d0cae6da09b8ce271c13e44f8ac69f51e4e0f",
|
||||
strip_prefix = "protobuf-25.5",
|
||||
urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v25.5/protobuf-25.5.tar.gz"],
|
||||
)
|
||||
|
||||
def io_grpc_grpc_proto():
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.concurrent.ArrayBlockingQueue;
|
|||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
@ -69,7 +70,7 @@ public final class BlockingClientCall<ReqT, RespT> {
|
|||
private final ThreadSafeThreadlessExecutor executor;
|
||||
|
||||
private boolean writeClosed;
|
||||
private volatile Status closedStatus; // null if not closed
|
||||
private AtomicReference<CloseState> closeState = new AtomicReference<>();
|
||||
|
||||
BlockingClientCall(ClientCall<ReqT, RespT> call, ThreadSafeThreadlessExecutor executor) {
|
||||
this.call = call;
|
||||
|
@ -120,22 +121,22 @@ public final class BlockingClientCall<ReqT, RespT> {
|
|||
logger.finer("Client Blocking read had value: " + bufferedValue);
|
||||
}
|
||||
|
||||
Status currentClosedStatus;
|
||||
CloseState currentCloseState;
|
||||
if (bufferedValue != null) {
|
||||
call.request(1);
|
||||
return bufferedValue;
|
||||
} else if ((currentClosedStatus = closedStatus) == null) {
|
||||
} else if ((currentCloseState = closeState.get()) == null) {
|
||||
throw new IllegalStateException(
|
||||
"The message disappeared... are you reading from multiple threads?");
|
||||
} else if (!currentClosedStatus.isOk()) {
|
||||
throw currentClosedStatus.asException();
|
||||
} else if (!currentCloseState.status.isOk()) {
|
||||
throw currentCloseState.status.asException(currentCloseState.trailers);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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;
|
||||
if (currentClosedStatus != null && !currentClosedStatus.isOk()) {
|
||||
throw currentClosedStatus.asException();
|
||||
CloseState currentCloseState = closeState.get();
|
||||
if (currentCloseState != null && !currentCloseState.status.isOk()) {
|
||||
throw currentCloseState.status.asException(currentCloseState.trailers);
|
||||
}
|
||||
|
||||
return !buffer.isEmpty();
|
||||
|
@ -221,17 +222,16 @@ public final class BlockingClientCall<ReqT, RespT> {
|
|||
}
|
||||
|
||||
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);
|
||||
Status savedClosedStatus = closedStatus;
|
||||
if (savedClosedStatus == null) {
|
||||
CloseState savedCloseState = closeState.get();
|
||||
if (savedCloseState == null || savedCloseState.status == null) {
|
||||
call.sendMessage(request);
|
||||
return true;
|
||||
} else if (savedClosedStatus.isOk()) {
|
||||
} else if (savedCloseState.status.isOk()) {
|
||||
return false;
|
||||
} else {
|
||||
// Propagate any errors returned from the server
|
||||
throw savedClosedStatus.asException();
|
||||
throw savedCloseState.status.asException(savedCloseState.trailers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,7 +274,8 @@ public final class BlockingClientCall<ReqT, RespT> {
|
|||
@VisibleForTesting
|
||||
Status getClosedStatus() {
|
||||
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
|
||||
*/
|
||||
private boolean isWriteLegal() {
|
||||
return !writeClosed && closedStatus == null;
|
||||
return !writeClosed && closeState.get() == null;
|
||||
}
|
||||
|
||||
ClientCall.Listener<RespT> getListener() {
|
||||
|
@ -335,15 +336,25 @@ public final class BlockingClientCall<ReqT, RespT> {
|
|||
private final class QueuingListener extends ClientCall.Listener<RespT> {
|
||||
@Override
|
||||
public void onMessage(RespT value) {
|
||||
Preconditions.checkState(closedStatus == null, "ClientCall already closed");
|
||||
Preconditions.checkState(closeState.get() == null, "ClientCall already closed");
|
||||
buffer.add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Status status, Metadata trailers) {
|
||||
Preconditions.checkState(closedStatus == null, "ClientCall already closed");
|
||||
closedStatus = status;
|
||||
CloseState newCloseState = new CloseState(status, trailers);
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
ConnectivityState oldState = connectivityState;
|
||||
connectivityState = newState;
|
||||
picker = newPicker;
|
||||
|
||||
if (deletionTimer != null && deletionTimer.isPending()) {
|
||||
return;
|
||||
}
|
||||
if (newState.equals(CONNECTING)) {
|
||||
if (newState.equals(CONNECTING) && !oldState.equals(newState)) {
|
||||
if (!failOverTimer.isPending() && seenReadyOrIdleSinceTransientFailure) {
|
||||
failOverTimer = syncContext.schedule(new FailOverTask(), 10, TimeUnit.SECONDS,
|
||||
executor);
|
||||
|
|
|
@ -48,6 +48,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
@ -738,6 +739,32 @@ final class WeightedRoundRobinLoadBalancer extends MultiChildLoadBalancer {
|
|||
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 {
|
||||
long blackoutPeriodNanos = 10_000_000_000L; // 10s
|
||||
long weightExpirationPeriodNanos = 180_000_000_000L; //3min
|
||||
|
|
|
@ -109,7 +109,7 @@ final class ControlPlaneClient {
|
|||
this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
|
||||
this.messagePrinter = checkNotNull(messagePrinter, "messagePrinter");
|
||||
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.log(XdsLogLevel.INFO, "Created");
|
||||
}
|
||||
|
|
|
@ -676,6 +676,8 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
|||
private ResourceMetadata metadata;
|
||||
@Nullable
|
||||
private String errorDescription;
|
||||
@Nullable
|
||||
private Status lastError;
|
||||
|
||||
ResourceSubscriber(XdsResourceType<T> type, String resource) {
|
||||
syncContext.throwIfNotInThisSynchronizationContext();
|
||||
|
@ -712,11 +714,16 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
|||
watchers.put(watcher, watcherExecutor);
|
||||
T savedData = data;
|
||||
boolean savedAbsent = absent;
|
||||
Status savedError = lastError;
|
||||
watcherExecutor.execute(() -> {
|
||||
if (errorDescription != null) {
|
||||
watcher.onError(Status.INVALID_ARGUMENT.withDescription(errorDescription));
|
||||
return;
|
||||
}
|
||||
if (savedError != null) {
|
||||
watcher.onError(savedError);
|
||||
return;
|
||||
}
|
||||
if (savedData != null) {
|
||||
notifyWatcher(watcher, savedData);
|
||||
} else if (savedAbsent) {
|
||||
|
@ -808,6 +815,7 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
|||
this.metadata = ResourceMetadata
|
||||
.newResourceMetadataAcked(parsedResource.getRawResource(), version, updateTime);
|
||||
absent = false;
|
||||
lastError = null;
|
||||
if (resourceDeletionIgnored) {
|
||||
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}",
|
||||
|
@ -857,6 +865,7 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
|||
if (!absent) {
|
||||
data = null;
|
||||
absent = true;
|
||||
lastError = null;
|
||||
metadata = serverInfo.resourceTimerIsTransientError()
|
||||
? ResourceMetadata.newResourceMetadataTimeout()
|
||||
: ResourceMetadata.newResourceMetadataDoesNotExist();
|
||||
|
@ -894,6 +903,7 @@ public final class XdsClientImpl extends XdsClient implements ResourceStore {
|
|||
Status errorAugmented = Status.fromCode(error.getCode())
|
||||
.withDescription(description + "nodeID: " + bootstrapInfo.node().getId())
|
||||
.withCause(error.getCause());
|
||||
this.lastError = errorAugmented;
|
||||
|
||||
for (ResourceWatcher<T> watcher : watchers.keySet()) {
|
||||
if (tracker != null) {
|
||||
|
|
|
@ -285,6 +285,8 @@ public abstract class GrpcXdsClientImplTestBase {
|
|||
@Mock
|
||||
private ResourceWatcher<LdsUpdate> ldsResourceWatcher;
|
||||
@Mock
|
||||
private ResourceWatcher<LdsUpdate> ldsResourceWatcher2;
|
||||
@Mock
|
||||
private ResourceWatcher<RdsUpdate> rdsResourceWatcher;
|
||||
@Mock
|
||||
private ResourceWatcher<CdsUpdate> cdsResourceWatcher;
|
||||
|
@ -694,6 +696,24 @@ public abstract class GrpcXdsClientImplTestBase {
|
|||
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
|
||||
public void ldsResponseErrorHandling_allResourcesFailedUnpack() {
|
||||
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.clearInvocations;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
@ -70,6 +71,7 @@ import org.junit.runner.RunWith;
|
|||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
@ -554,6 +556,55 @@ public class PriorityLoadBalancerTest {
|
|||
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
|
||||
public void readyToConnectDoesNotFailOverButUpdatesPicker() {
|
||||
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.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.testing.EqualsTester;
|
||||
import com.google.protobuf.Duration;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.CallOptions;
|
||||
|
@ -215,6 +216,40 @@ public class WeightedRoundRobinLoadBalancerTest {
|
|||
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
|
||||
public void wrrLifeCycle() {
|
||||
syncContext.execute(() -> wrr.acceptResolvedAddresses(ResolvedAddresses.newBuilder()
|
||||
|
|
Loading…
Reference in New Issue