diff --git a/WORKSPACE b/WORKSPACE index 0f20ad0b8..667d97741 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -31,14 +31,18 @@ http_archive( # rules_proto defines proto_library, as well as @com_google_protobuf_javalite http_archive( name = "rules_proto", - sha256 = "dc3fb206a2cb3441b485eb1e423165b231235a1ea9b031b4433cf7bc1fa460dd", - strip_prefix = "rules_proto-5.3.0-21.7", - urls = [ - "https://github.com/bazelbuild/rules_proto/archive/refs/tags/5.3.0-21.7.tar.gz", - ], + sha256 = "6fb6767d1bef535310547e03247f7518b03487740c11b6c6adb7952033fe1295", + strip_prefix = "rules_proto-6.0.2", + url = "https://github.com/bazelbuild/rules_proto/releases/download/6.0.2/rules_proto-6.0.2.tar.gz", ) -load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") + +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") rules_proto_dependencies() + +load("@rules_proto//proto:setup.bzl", "rules_proto_setup") +rules_proto_setup() + +load("@rules_proto//proto:toolchains.bzl", "rules_proto_toolchains") rules_proto_toolchains() load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") diff --git a/repo.bzl b/repo.bzl index 526da15c9..4d862faee 100644 --- a/repo.bzl +++ b/repo.bzl @@ -124,10 +124,9 @@ def android_test_repositories(with_dev_repositories = False): # Protobuf http_archive( name = "com_google_protobuf", - sha256 = "d82eb0141ad18e98de47ed7ed415daabead6d5d1bef1b8cccb6aa4d108a9008f", - strip_prefix = "protobuf-b4f193788c9f0f05d7e0879ea96cd738630e5d51", - # Commit from 2019-05-15, update to protobuf 3.8 when available. - url = "https://github.com/protocolbuffers/protobuf/archive/b4f193788c9f0f05d7e0879ea96cd738630e5d51.tar.gz", + sha256 = "b2340aa47faf7ef10a0328190319d3f3bee1b24f426d4ce8f4253b6f27ce16db", + strip_prefix = "protobuf-28.2", + url = "https://github.com/protocolbuffers/protobuf/releases/download/v28.2/protobuf-28.2.tar.gz", ) # Protobuf's dependencies @@ -137,21 +136,24 @@ def android_test_repositories(with_dev_repositories = False): http_archive( name = "zlib", build_file = "@com_google_protobuf//:third_party/zlib.BUILD", - sha256 = "91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9", - strip_prefix = "zlib-1.2.12", - urls = ["https://zlib.net/zlib-1.2.12.tar.gz"], + sha256 = "9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23", + strip_prefix = "zlib-1.3.1", + urls = ["https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz"], ) http_archive( name = "bazel_skylib", - url = "https://github.com/bazelbuild/bazel-skylib/releases/download/0.8.0/bazel-skylib.0.8.0.tar.gz", - sha256 = "2ef429f5d7ce7111263289644d233707dba35e39696377ebab8b0bc701f7818e", + sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", + ], ) # Open source version of the google python flags library. http_archive( name = "absl_py", - sha256 = "980ce58c34dfa75a9d20d45c355658191c166557f1de41ab52f208bd00604c2b", - strip_prefix = "abseil-py-b347ba6022370f895d3133241ed96965b95ecb40", - urls = ["https://github.com/abseil/abseil-py/archive/b347ba6022370f895d3133241ed96965b95ecb40.tar.gz"], + sha256 = "8a3d0830e4eb4f66c4fa907c06edf6ce1c719ced811a12e26d9d3162f8471758", + strip_prefix = "abseil-py-2.1.0", + urls = ["https://github.com/abseil/abseil-py/archive/refs/tags/v2.1.0.tar.gz"], ) diff --git a/services/CHANGELOG.md b/services/CHANGELOG.md index 9d62f882d..09bed8125 100644 --- a/services/CHANGELOG.md +++ b/services/CHANGELOG.md @@ -11,6 +11,8 @@ **New Features** +* Added a new protocol to replace SpeakEasy. + **Breaking Changes** **API Changes** diff --git a/services/shellexecutor/java/androidx/test/services/shellexecutor/BUILD b/services/shellexecutor/java/androidx/test/services/shellexecutor/BUILD index c1257b98d..0d5eb54c9 100644 --- a/services/shellexecutor/java/androidx/test/services/shellexecutor/BUILD +++ b/services/shellexecutor/java/androidx/test/services/shellexecutor/BUILD @@ -29,6 +29,32 @@ kt_android_library( ], ) +proto_library( + name = "local_socket_protocol_pb", + srcs = ["local_socket_protocol.proto"], +) + +java_lite_proto_library( + name = "local_socket_protocol_pb_java_proto_lite", + visibility = [ + "//services/shellexecutor/javatests/androidx/test/services/shellexecutor:__subpackages__", + ], + deps = [":local_socket_protocol_pb"], +) + +kt_android_library( + name = "local_socket_protocol", + srcs = ["LocalSocketProtocol.kt"], + visibility = [ + "//services/shellexecutor/javatests/androidx/test/services/shellexecutor:__subpackages__", + ], + deps = [ + ":local_socket_protocol_pb_java_proto_lite", + "@com_google_protobuf//:protobuf_javalite", + "@maven//:org_jetbrains_kotlinx_kotlinx_coroutines_core", + ], +) + kt_android_library( name = "exec_server", srcs = [ diff --git a/services/shellexecutor/java/androidx/test/services/shellexecutor/LocalSocketProtocol.kt b/services/shellexecutor/java/androidx/test/services/shellexecutor/LocalSocketProtocol.kt new file mode 100644 index 000000000..c36dea437 --- /dev/null +++ b/services/shellexecutor/java/androidx/test/services/shellexecutor/LocalSocketProtocol.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 androidx.test.services.shellexecutor + +import android.net.LocalSocket +import android.net.LocalSocketAddress +import android.util.Log +import androidx.test.services.shellexecutor.LocalSocketProtocolProto.RunCommandRequest +import androidx.test.services.shellexecutor.LocalSocketProtocolProto.RunCommandResponse +import com.google.protobuf.ByteString +import java.io.IOException +import java.net.URLDecoder +import java.net.URLEncoder +import kotlin.time.Duration + +/** + * Protocol for ShellCommandLocalSocketClient to talk to ShellCommandLocalSocketExecutorServer. + * + * Since androidx.test.services already includes the protobuf runtime, we aren't paying much extra + * for adding some more protos to ship back and forth, which is vastly easier to deal with than + * PersistableBundles (which don't even support ByteArray types). + * + * A conversation consists of a single RunCommandRequest from the client followed by a stream of + * RunCommandResponses from the server; the final response has an exit code. + */ +object LocalSocketProtocol { + /** Composes a RunCommandRequest and sends it over the LocalSocket. */ + fun LocalSocket.sendRequest( + argv: List, + env: Map? = null, + timeout: Duration, + ) { + val builder = RunCommandRequest.newBuilder() + builder.addAllArgv(argv) + env?.forEach { (k, v) -> builder.putEnvironment(k, v) } + builder.setTimeoutMs(timeout.inWholeMilliseconds) + builder.build().writeDelimitedTo(outputStream) + } + + /** Reads a RunCommandRequest from the LocalSocket. */ + fun LocalSocket.readRequest(): RunCommandRequest { + return RunCommandRequest.parseDelimitedFrom(inputStream)!! + } + + /** Composes a RunCommandResponse and sends it over the LocalSocket. */ + fun LocalSocket.sendResponse( + buffer: ByteArray? = null, + size: Int = 0, + exitCode: Int? = null, + ): Boolean { + val builder = RunCommandResponse.newBuilder() + buffer?.let { + val bufferSize = if (size > 0) size else it.size + builder.buffer = ByteString.copyFrom(it, 0, bufferSize) + } + exitCode?.let { builder.exitCode = it } + + try { + builder.build().writeDelimitedTo(outputStream) + } catch (x: IOException) { + // Sadly, the only way to discover that the client cut the connection is an exception that + // can only be distinguished by its text. + if (x.message.equals("Broken pipe")) { + Log.i(TAG, "LocalSocket stream closed early") + } else { + Log.w(TAG, "LocalSocket write failed", x) + } + return false + } + return true + } + + /** Reads a RunCommandResponse from the LocalSocket. */ + fun LocalSocket.readResponse(): RunCommandResponse? { + return RunCommandResponse.parseDelimitedFrom(inputStream) + } + + /** + * The address can contain spaces, and since it gets passed through a command line, we need to + * encode it so it doesn't get split by argv. java.net.URLEncoder is conveniently available on all + * SDK versions. + */ + @JvmStatic fun LocalSocketAddress.asBinderKey() = ":${URLEncoder.encode(name, "UTF-8")}" + + @JvmStatic + fun fromBinderKey(binderKey: String) = + LocalSocketAddress(URLDecoder.decode(binderKey.trimStart(':'), "UTF-8")) + + @JvmStatic fun isBinderKey(maybeKey: String) = maybeKey.startsWith(':') + + const val TAG = "LocalSocketProtocol" +} diff --git a/services/shellexecutor/java/androidx/test/services/shellexecutor/local_socket_protocol.proto b/services/shellexecutor/java/androidx/test/services/shellexecutor/local_socket_protocol.proto new file mode 100644 index 000000000..f92259694 --- /dev/null +++ b/services/shellexecutor/java/androidx/test/services/shellexecutor/local_socket_protocol.proto @@ -0,0 +1,36 @@ +// +// Copyright (C) 2024 The Android Open Source Project +// +// 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. + +edition = "2023"; +//syntax = "proto3"; // Maven on github doesn't like 'edition = "2023"' + +package androidx.test.services.storage; + +option java_package = "androidx.test.services.shellexecutor"; +option java_outer_classname = 'LocalSocketProtocolProto'; + +// Message sent from client to server to start a process. +message RunCommandRequest { + repeated string argv = 1; + map environment = 2; + int64 timeout_ms = 3; +} + +// Multiple responses can be streamed back to the client. The one that has an +// exit code indicates the end of the stream. +message RunCommandResponse { + bytes buffer = 1; + int32 exit_code = 2; +}