diff --git a/automotive/remoteaccess/Android.bp b/automotive/remoteaccess/Android.bp new file mode 100644 index 0000000000..ac04354609 --- /dev/null +++ b/automotive/remoteaccess/Android.bp @@ -0,0 +1,39 @@ +// Copyright (C) 2022 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 { + default_applicable_licenses: ["hardware_interfaces_license"], +} + +aidl_interface { + name: "android.hardware.automotive.remoteaccess", + vendor_available: true, + srcs: [ + "android/hardware/automotive/remoteaccess/**/*.aidl", + ], + stability: "vintf", + backend: { + cpp: { + enabled: false, + }, + java: { + sdk_version: "module_current", + min_sdk_version: "31", + apex_available: [ + "//apex_available:platform", + "com.android.car.framework", + ], + }, + }, +} diff --git a/automotive/remoteaccess/OWNERS b/automotive/remoteaccess/OWNERS new file mode 100644 index 0000000000..d6969e5eb5 --- /dev/null +++ b/automotive/remoteaccess/OWNERS @@ -0,0 +1,2 @@ +ericjeong@google.com +shanyu@google.com diff --git a/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/ApState.aidl b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/ApState.aidl new file mode 100644 index 0000000000..da4f1d4304 --- /dev/null +++ b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/ApState.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.remoteaccess; +@VintfStability +parcelable ApState { + boolean isReadyForRemoteTask; + boolean isWakeupRequired; +} diff --git a/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl new file mode 100644 index 0000000000..9b6eb2f297 --- /dev/null +++ b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.remoteaccess; +@VintfStability +interface IRemoteAccess { + String getDeviceId(); + String getWakeupServiceName(); + void setRemoteTaskCallback(android.hardware.automotive.remoteaccess.IRemoteTaskCallback callback); + void clearRemoteTaskCallback(); + void notifyApStateChange(in android.hardware.automotive.remoteaccess.ApState state); +} diff --git a/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl new file mode 100644 index 0000000000..295100ea51 --- /dev/null +++ b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.remoteaccess; +@VintfStability +interface IRemoteTaskCallback { + oneway void onRemoteTaskRequested(String clientId, in byte[] data); +} diff --git a/automotive/remoteaccess/android/hardware/automotive/remoteaccess/ApState.aidl b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/ApState.aidl new file mode 100644 index 0000000000..c8eb3eff8d --- /dev/null +++ b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/ApState.aidl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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 android.hardware.automotive.remoteaccess; + +@VintfStability +parcelable ApState { + /** + * Whether AP (application processor) is ready to receive remote tasks. + * + * If this is true. AP is powered on and the car service is ready to handle + * remote tasks. + */ + boolean isReadyForRemoteTask; + /** + * Whether AP (application processor) needs to be woken up. + * + * While the AP is shutting down, this will be set to false to prevent the + * wakeup signal to interrupt the shutdown process. At the last step of the + * shutdown process, this will be set to true so that AP will be waken + * up when task arrives. After AP starts up, this will be set to false + * to prevent unnecessary wakeup signal. + */ + boolean isWakeupRequired; +} diff --git a/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl new file mode 100644 index 0000000000..a198b038a5 --- /dev/null +++ b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 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 android.hardware.automotive.remoteaccess; + +import android.hardware.automotive.remoteaccess.ApState; +import android.hardware.automotive.remoteaccess.IRemoteTaskCallback; + +/** + * Interface representing a remote wakeup client. + * + * A wakeup client is a binary outside Android framework that communicates with + * a wakeup server and receives wake up command. + */ +@VintfStability +interface IRemoteAccess { + /** + * Gets a unique device ID that could be recognized by wake up server. + * + * This device ID is provisioned during car production and is registered + * with the wake up server. + * + * @return a unique device ID. + */ + String getDeviceId(); + + /** + * Gets the name for the remote wakeup server. + * + * This name will be provided to remote task server during registration + * and used by remote task server to find the remote wakeup server to + * use for waking up the device. This name must be pre-negotiated between + * the remote wakeup server/client and the remote task server/client and + * must be unique. We recommend the format to be a human readable string + * with reverse domain name notation (reverse-DNS), e.g. + * "com.google.vehicle.wakeup". + */ + String getWakeupServiceName(); + + /** + * Sets a callback to be called when a remote task is requested. + * + * @param callback A callback to be called when a remote task is requested. + */ + void setRemoteTaskCallback(IRemoteTaskCallback callback); + + /** + * Clears a previously set remote task callback. + * + * If no callback was set, this operation is no-op. + */ + void clearRemoteTaskCallback(); + + /** + * Notifies whether AP is ready to receive remote tasks. + * + *

Wakeup client should store and use this state until a new call with a + * different state arrives. + * + *

If {@code isReadyForRemoteTask} is true, the wakeup client may send + * the task received from the server to AP immediately. + * + *

If {@code isReadyForRemoteTask} is false, it must store the received + * remote tasks and wait until AP is ready to receive tasks. If it takes too + * long for AP to become ready, the task must be reported to remote task + * server as failed. Implementation must make sure no duplicate tasks are + * delivered to AP. + * + *

If {@code isWakeupRequired} is true, it must try to wake up AP when a + * remote task arrives or when there are pending requests. + * + *

If {@code isWakeupRequired} is false, it must not try to wake up AP. + */ + void notifyApStateChange(in ApState state); +} diff --git a/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl new file mode 100644 index 0000000000..7a1616f107 --- /dev/null +++ b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 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 android.hardware.automotive.remoteaccess; + +/** + * The callback interface for car service to receive tasks from wakup client. + */ +@VintfStability +interface IRemoteTaskCallback { + /** + * A callback that is called when a remote task is requested. + * + * @param clientId An ID to uniquely identify a remote task client. + * @param data Opaque task data passed to the remote task client. + */ + oneway void onRemoteTaskRequested(String clientId, in byte[] data); +} diff --git a/automotive/remoteaccess/hal/default/Android.bp b/automotive/remoteaccess/hal/default/Android.bp new file mode 100644 index 0000000000..a2bf86c9e5 --- /dev/null +++ b/automotive/remoteaccess/hal/default/Android.bp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_binary { + name: "android.hardware.automotive.remoteaccess@V1-default-service", + vendor: true, + vintf_fragments: ["remoteaccess-default-service.xml"], + init_rc: ["remoteaccess-default-service.rc"], + relative_install_path: "hw", + srcs: ["src/RemoteAccessImpl.cpp"], + whole_static_libs: [ + "RemoteAccessService", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "liblog", + "libutils", + "libgrpc++", + "libprotobuf-cpp-full", + ], + defaults: [ + "vhalclient_defaults", + ], + cflags: [ + "-Wno-unused-parameter", + "-DGRPC_SERVICE_ADDRESS=\"localhost:50051\"", + ], +} + +cc_library { + name: "RemoteAccessService", + vendor_available: true, + local_include_dirs: ["include"], + export_include_dirs: ["include"], + srcs: [ + "src/RemoteAccessService.cpp", + ], + whole_static_libs: [ + "android.hardware.automotive.remoteaccess-V1-ndk", + "wakeup_client_protos", + "libvhalclient", + ], + defaults: [ + "vhalclient_defaults", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libcutils", + "liblog", + "libutils", + "libgrpc++", + "libprotobuf-cpp-full", + ], + cflags: [ + "-Wno-unused-parameter", + ], +} + +cc_fuzz { + name: "android.hardware.automotive.remoteaccess@V1-default-service.aidl_fuzzer", + srcs: ["fuzzer/fuzzer.cpp"], + whole_static_libs: [ + "RemoteAccessService", + ], + static_libs: [ + "libgtest", + "libgmock", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "liblog", + "libutils", + "libgrpc++", + "libprotobuf-cpp-full", + ], + defaults: [ + "vhalclient_defaults", + "service_fuzzer_defaults", + ], + cflags: [ + "-Wno-unused-parameter", + "-DGRPC_SERVICE_ADDRESS=\"localhost:50051\"", + ], + fuzz_config: { + cc: [ + "shanyu@google.com", + ], + }, +} diff --git a/automotive/remoteaccess/hal/default/fuzzer/fuzzer.cpp b/automotive/remoteaccess/hal/default/fuzzer/fuzzer.cpp new file mode 100644 index 0000000000..292c80e7b6 --- /dev/null +++ b/automotive/remoteaccess/hal/default/fuzzer/fuzzer.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 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. + */ + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace remoteaccess { + +using ::grpc::ClientAsyncReaderInterface; +using ::grpc::ClientAsyncResponseReaderInterface; +using ::grpc::ClientContext; +using ::grpc::ClientReader; +using ::grpc::ClientReaderInterface; +using ::grpc::CompletionQueue; +using ::grpc::Status; +using ::grpc::testing::MockClientReader; +using ::testing::_; +using ::testing::Return; + +class MockGrpcClientStub : public WakeupClient::StubInterface { + public: + ClientReaderInterface* GetRemoteTasksRaw( + [[maybe_unused]] ClientContext* context, + [[maybe_unused]] const GetRemoteTasksRequest& request) override { + MockClientReader* mockClientReader = + new MockClientReader(); + ON_CALL(*mockClientReader, Finish()).WillByDefault(Return(Status::OK)); + ON_CALL(*mockClientReader, Read(_)).WillByDefault(Return(false)); + return mockClientReader; + } + + Status NotifyWakeupRequired([[maybe_unused]] ClientContext* context, + [[maybe_unused]] const NotifyWakeupRequiredRequest& request, + [[maybe_unused]] NotifyWakeupRequiredResponse* response) { + return Status::OK; + } + + // Async methods which we do not care. + ClientAsyncReaderInterface* AsyncGetRemoteTasksRaw( + [[maybe_unused]] ClientContext* context, + [[maybe_unused]] const GetRemoteTasksRequest& request, + [[maybe_unused]] CompletionQueue* cq, [[maybe_unused]] void* tag) { + return nullptr; + } + + ClientAsyncReaderInterface* PrepareAsyncGetRemoteTasksRaw( + [[maybe_unused]] ClientContext* context, + [[maybe_unused]] const GetRemoteTasksRequest& request, + [[maybe_unused]] CompletionQueue* cq) { + return nullptr; + } + + ClientAsyncResponseReaderInterface* AsyncNotifyWakeupRequiredRaw( + [[maybe_unused]] ClientContext* context, + [[maybe_unused]] const NotifyWakeupRequiredRequest& request, + [[maybe_unused]] CompletionQueue* cq) { + return nullptr; + } + + ClientAsyncResponseReaderInterface* + PrepareAsyncNotifyWakeupRequiredRaw([[maybe_unused]] ClientContext* context, + [[maybe_unused]] const NotifyWakeupRequiredRequest& request, + [[maybe_unused]] CompletionQueue* c) { + return nullptr; + } +}; + +} // namespace remoteaccess +} // namespace automotive +} // namespace hardware +} // namespace android + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + android::hardware::automotive::remoteaccess::MockGrpcClientStub stub; + std::shared_ptr service = + ndk::SharedRefBase::make< + android::hardware::automotive::remoteaccess::RemoteAccessService>(&stub); + android::fuzzService(service->asBinder().get(), FuzzedDataProvider(data, size)); + + return 0; +} diff --git a/automotive/remoteaccess/hal/default/include/RemoteAccessService.h b/automotive/remoteaccess/hal/default/include/RemoteAccessService.h new file mode 100644 index 0000000000..74c2af4c9f --- /dev/null +++ b/automotive/remoteaccess/hal/default/include/RemoteAccessService.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace remoteaccess { + +// A IRemoteTaskCallback implementation for debug purpose. +class DebugRemoteTaskCallback final + : public aidl::android::hardware::automotive::remoteaccess::BnRemoteTaskCallback { + public: + DebugRemoteTaskCallback() { mStartTimeMillis = android::uptimeMillis(); }; + + ndk::ScopedAStatus onRemoteTaskRequested(const std::string& clientId, + const std::vector& data) override; + std::string printTasks(); + + private: + struct TaskData { + std::string clientId; + std::vector data; + }; + + std::mutex mLock; + int64_t mStartTimeMillis; + std::vector mTasks; +}; + +class RemoteAccessService + : public aidl::android::hardware::automotive::remoteaccess::BnRemoteAccess { + public: + explicit RemoteAccessService(WakeupClient::StubInterface* grpcStub); + + ~RemoteAccessService(); + + ndk::ScopedAStatus getDeviceId(std::string* deviceId) override; + + ndk::ScopedAStatus getWakeupServiceName(std::string* wakeupServiceName) override; + + ndk::ScopedAStatus setRemoteTaskCallback( + const std::shared_ptr< + aidl::android::hardware::automotive::remoteaccess::IRemoteTaskCallback>& + callback) override; + + ndk::ScopedAStatus clearRemoteTaskCallback() override; + + ndk::ScopedAStatus notifyApStateChange( + const aidl::android::hardware::automotive::remoteaccess::ApState& newState) override; + + binder_status_t dump(int fd, const char** args, uint32_t numArgs) override; + + private: + // For testing. + friend class RemoteAccessServiceUnitTest; + + static bool checkDumpPermission(); + + WakeupClient::StubInterface* mGrpcStub; + std::thread mThread; + std::mutex mLock; + std::condition_variable mCv; + std::shared_ptr + mRemoteTaskCallback GUARDED_BY(mLock); + std::unique_ptr mGetRemoteTasksContext GUARDED_BY(mLock); + // Associated with mCv to notify the task loop to stop waiting and exit. + bool mTaskWaitStopped GUARDED_BY(mLock); + // A mutex to make sure startTaskLoop does not overlap with stopTaskLoop. + std::mutex mStartStopTaskLoopLock; + bool mTaskLoopRunning GUARDED_BY(mStartStopTaskLoopLock); + // Default wait time before retry connecting to remote access client is 10s. + size_t mRetryWaitInMs = 10'000; + std::shared_ptr mDebugCallback; + + void runTaskLoop(); + void maybeStartTaskLoop(); + void maybeStopTaskLoop(); + ndk::ScopedAStatus getDeviceIdWithClient( + android::frameworks::automotive::vhal::IVhalClient& client, std::string* deviceId); + + void setRetryWaitInMs(size_t retryWaitInMs) { mRetryWaitInMs = retryWaitInMs; } + void dumpHelp(int fd); +}; + +} // namespace remoteaccess +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/remoteaccess/hal/default/proto/Android.bp b/automotive/remoteaccess/hal/default/proto/Android.bp new file mode 100644 index 0000000000..3e0dba14e5 --- /dev/null +++ b/automotive/remoteaccess/hal/default/proto/Android.bp @@ -0,0 +1,79 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +genrule { + name: "wakeup_client_pb_h", + tools: [ + "aprotoc", + "protoc-gen-grpc-cpp-plugin", + ], + cmd: "$(location aprotoc) -I$$(dirname $(in)) -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)", + srcs: [ + "wakeup_client.proto", + ], + out: [ + "wakeup_client.pb.h", + "wakeup_client.grpc.pb.h", + ], +} + +genrule { + name: "wakeup_client_pb_cc", + tools: [ + "aprotoc", + "protoc-gen-grpc-cpp-plugin", + ], + cmd: "$(location aprotoc) -I$$(dirname $(in)) -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)", + srcs: [ + "wakeup_client.proto", + ], + out: [ + "wakeup_client.pb.cc", + "wakeup_client.grpc.pb.cc", + ], +} + +cc_library_static { + name: "wakeup_client_protos", + vendor_available: true, + host_supported: true, + include_dirs: [ + "external/protobuf/src", + ], + generated_headers: [ + "wakeup_client_pb_h", + ], + export_generated_headers: [ + "wakeup_client_pb_h", + ], + generated_sources: [ + "wakeup_client_pb_cc", + ], + shared_libs: [ + "libgrpc++", + "libprotobuf-cpp-full", + ], + cflags: [ + "-Wno-unused-parameter", + ], +} diff --git a/automotive/remoteaccess/hal/default/proto/wakeup_client.proto b/automotive/remoteaccess/hal/default/proto/wakeup_client.proto new file mode 100644 index 0000000000..4fe0d0177b --- /dev/null +++ b/automotive/remoteaccess/hal/default/proto/wakeup_client.proto @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 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. + */ + +syntax = "proto3"; + +package android.hardware.automotive.remoteaccess; + +/** + * Service provided by a wakeup client running on TCU. + */ +service WakeupClient { + /** + * Establish a long-live connection to receive remote tasks. + * + *

For the server, whenever a remote task arrives, if the connection is + * alive, it will use the return stream to return a task's information. + * + *

If the connection is not alive, the server must stores the remote task + * until a new connection is established (which means AP is ready to + * receive remote task again) and send the stored tasks. + * + *

If the server closes the connection, the client will try to + * reestablish the connection. + */ + rpc GetRemoteTasks(GetRemoteTasksRequest) returns (stream GetRemoteTasksResponse) {} + + /** + * Notifies whether AP is required to be waken up when remote task arrives. + * + *

Wakeup client should store and use this state until a new call with a + * different state arrives. + * + *

If {@code isWakeupRequired} in the request is true, it must wake up AP + * when a remote task arrives. + * + *

If {@code isWakeupRequired} in the request is false, it must not try + * to wake up AP. + */ + rpc NotifyWakeupRequired(NotifyWakeupRequiredRequest) returns (NotifyWakeupRequiredResponse) {} +} + +message GetRemoteTasksRequest {} + +message GetRemoteTasksResponse { + string clientId = 1; + bytes data = 2; +} + +message NotifyWakeupRequiredRequest { + bool isWakeupRequired = 1; +} + +message NotifyWakeupRequiredResponse {} diff --git a/automotive/remoteaccess/hal/default/remoteaccess-default-service.rc b/automotive/remoteaccess/hal/default/remoteaccess-default-service.rc new file mode 100644 index 0000000000..b7a9cdc82a --- /dev/null +++ b/automotive/remoteaccess/hal/default/remoteaccess-default-service.rc @@ -0,0 +1,4 @@ +service vendor.remoteaccess-default /vendor/bin/hw/android.hardware.automotive.remoteaccess@V1-default-service + class hal + user vehicle_network + group system inet diff --git a/automotive/remoteaccess/hal/default/remoteaccess-default-service.xml b/automotive/remoteaccess/hal/default/remoteaccess-default-service.xml new file mode 100644 index 0000000000..d050a1b646 --- /dev/null +++ b/automotive/remoteaccess/hal/default/remoteaccess-default-service.xml @@ -0,0 +1,7 @@ + + + android.hardware.automotive.remoteaccess + 1 + IRemoteAccess/default + + diff --git a/automotive/remoteaccess/hal/default/src/RemoteAccessImpl.cpp b/automotive/remoteaccess/hal/default/src/RemoteAccessImpl.cpp new file mode 100644 index 0000000000..8720c2f5c6 --- /dev/null +++ b/automotive/remoteaccess/hal/default/src/RemoteAccessImpl.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 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. + */ + +#define LOG_TAG "RemoteAccessImpl" + +#include "RemoteAccessService.h" + +#include +#include +#include +#include +#include + +constexpr char SERVICE_NAME[] = "android.hardware.automotive.remoteaccess.IRemoteAccess/default"; + +int main(int /* argc */, char* /* argv */[]) { + ALOGI("Registering RemoteAccessService as service..."); + +#ifndef GRPC_SERVICE_ADDRESS + ALOGE("GRPC_SERVICE_ADDRESS is not defined, exiting"); + exit(1); +#endif + auto channel = grpc::CreateChannel(GRPC_SERVICE_ADDRESS, grpc::InsecureChannelCredentials()); + auto clientStub = android::hardware::automotive::remoteaccess::WakeupClient::NewStub(channel); + auto service = ndk::SharedRefBase::make< + android::hardware::automotive::remoteaccess::RemoteAccessService>(clientStub.get()); + + binder_exception_t err = AServiceManager_addService(service->asBinder().get(), SERVICE_NAME); + if (err != EX_NONE) { + ALOGE("failed to register android.hardware.automotive.remote.IRemoteAccess service, " + "exception: %d", + err); + exit(1); + } + + if (!ABinderProcess_setThreadPoolMaxThreadCount(1)) { + ALOGE("%s", "failed to set thread pool max thread count"); + exit(1); + } + ABinderProcess_startThreadPool(); + + ALOGI("RemoteAccess service Ready"); + + ABinderProcess_joinThreadPool(); + + ALOGW("Should not reach here"); + + return 0; +} diff --git a/automotive/remoteaccess/hal/default/src/RemoteAccessService.cpp b/automotive/remoteaccess/hal/default/src/RemoteAccessService.cpp new file mode 100644 index 0000000000..5cd58d3bba --- /dev/null +++ b/automotive/remoteaccess/hal/default/src/RemoteAccessService.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "RemoteAccessService.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace remoteaccess { + +namespace { + +using ::aidl::android::hardware::automotive::remoteaccess::ApState; +using ::aidl::android::hardware::automotive::remoteaccess::IRemoteTaskCallback; +using ::aidl::android::hardware::automotive::vehicle::VehicleProperty; +using ::android::base::ScopedLockAssertion; +using ::android::base::StringAppendF; +using ::android::base::StringPrintf; +using ::android::frameworks::automotive::vhal::IVhalClient; +using ::android::hardware::automotive::vehicle::toInt; +using ::grpc::ClientContext; +using ::grpc::ClientReaderInterface; +using ::grpc::Status; +using ::grpc::StatusCode; +using ::ndk::ScopedAStatus; + +const std::string WAKEUP_SERVICE_NAME = "com.google.vehicle.wakeup"; +constexpr char COMMAND_SET_AP_STATE[] = "--set-ap-state"; +constexpr char COMMAND_START_DEBUG_CALLBACK[] = "--start-debug-callback"; +constexpr char COMMAND_STOP_DEBUG_CALLBACK[] = "--stop-debug-callback"; +constexpr char COMMAND_SHOW_TASK[] = "--show-task"; +constexpr char COMMAND_GET_DEVICE_ID[] = "--get-device-id"; + +std::vector stringToBytes(const std::string& s) { + const char* data = s.data(); + return std::vector(data, data + s.size()); +} + +ScopedAStatus rpcStatusToScopedAStatus(const Status& status, const std::string& errorMsg) { + return ScopedAStatus::fromServiceSpecificErrorWithMessage( + status.error_code(), (errorMsg + ", error: " + status.error_message()).c_str()); +} + +std::string printBytes(const std::vector& bytes) { + std::string s; + for (size_t i = 0; i < bytes.size(); i++) { + StringAppendF(&s, "%02x", bytes[i]); + } + return s; +} + +bool checkBoolFlag(const char* flag) { + return !strcmp(flag, "1") || !strcmp(flag, "0"); +} + +void dprintErrorStatus(int fd, const char* detail, const ScopedAStatus& status) { + dprintf(fd, "%s, code: %d, error: %s\n", detail, status.getStatus(), status.getMessage()); +} + +} // namespace + +RemoteAccessService::RemoteAccessService(WakeupClient::StubInterface* grpcStub) + : mGrpcStub(grpcStub){}; + +RemoteAccessService::~RemoteAccessService() { + maybeStopTaskLoop(); +} + +void RemoteAccessService::maybeStartTaskLoop() { + std::lock_guard lockGuard(mStartStopTaskLoopLock); + if (mTaskLoopRunning) { + return; + } + + mThread = std::thread([this]() { runTaskLoop(); }); + + mTaskLoopRunning = true; +} + +void RemoteAccessService::maybeStopTaskLoop() { + std::lock_guard lockGuard(mStartStopTaskLoopLock); + if (!mTaskLoopRunning) { + return; + } + + { + std::lock_guard lockGuard(mLock); + // Try to stop the reading stream. + if (mGetRemoteTasksContext) { + mGetRemoteTasksContext->TryCancel(); + mGetRemoteTasksContext.reset(); + } + mTaskWaitStopped = true; + mCv.notify_all(); + } + if (mThread.joinable()) { + mThread.join(); + } + + mTaskLoopRunning = false; +} + +void RemoteAccessService::runTaskLoop() { + GetRemoteTasksRequest request = {}; + std::unique_ptr> reader; + while (true) { + { + std::lock_guard lockGuard(mLock); + mGetRemoteTasksContext.reset(new ClientContext()); + reader = mGrpcStub->GetRemoteTasks(mGetRemoteTasksContext.get(), request); + } + GetRemoteTasksResponse response; + while (reader->Read(&response)) { + ALOGI("Receiving one task from remote task client"); + + std::shared_ptr callback; + { + std::lock_guard lockGuard(mLock); + callback = mRemoteTaskCallback; + } + if (callback == nullptr) { + ALOGD("No callback registered, task ignored"); + continue; + } + ALOGD("Calling onRemoteTaskRequested callback for client ID: %s", + response.clientid().c_str()); + ScopedAStatus callbackStatus = callback->onRemoteTaskRequested( + response.clientid(), stringToBytes(response.data())); + if (!callbackStatus.isOk()) { + ALOGE("Failed to call onRemoteTaskRequested callback, status: %d, message: %s", + callbackStatus.getStatus(), callbackStatus.getMessage()); + } + } + Status status = reader->Finish(); + + ALOGE("GetRemoteTasks stream breaks, code: %d, message: %s, sleeping for 10s and retry", + status.error_code(), status.error_message().c_str()); + // The long lasting connection should not return. But if the server returns, retry after + // 10s. + { + std::unique_lock lk(mLock); + if (mCv.wait_for(lk, std::chrono::milliseconds(mRetryWaitInMs), [this] { + ScopedLockAssertion lockAssertion(mLock); + return mTaskWaitStopped; + })) { + // If the stopped flag is set, we are quitting, exit the loop. + break; + } + } + } +} + +ScopedAStatus RemoteAccessService::getDeviceId(std::string* deviceId) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + auto vhalClient = IVhalClient::tryCreate(); + if (vhalClient == nullptr) { + ALOGE("Failed to connect to VHAL"); + return ScopedAStatus::fromServiceSpecificErrorWithMessage( + /*errorCode=*/0, "Failed to connect to VHAL to get device ID"); + } + return getDeviceIdWithClient(*vhalClient.get(), deviceId); +#else + // Don't use VHAL client in fuzzing since IPC is not allowed. + return ScopedAStatus::ok(); +#endif +} + +ScopedAStatus RemoteAccessService::getDeviceIdWithClient(IVhalClient& vhalClient, + std::string* deviceId) { + auto result = vhalClient.getValueSync( + *vhalClient.createHalPropValue(toInt(VehicleProperty::INFO_VIN))); + if (!result.ok()) { + return ScopedAStatus::fromServiceSpecificErrorWithMessage( + /*errorCode=*/0, + ("failed to get INFO_VIN from VHAL: " + result.error().message()).c_str()); + } + *deviceId = (*result)->getStringValue(); + return ScopedAStatus::ok(); +} + +ScopedAStatus RemoteAccessService::getWakeupServiceName(std::string* wakeupServiceName) { + *wakeupServiceName = WAKEUP_SERVICE_NAME; + return ScopedAStatus::ok(); +} + +ScopedAStatus RemoteAccessService::setRemoteTaskCallback( + const std::shared_ptr& callback) { + std::lock_guard lockGuard(mLock); + mRemoteTaskCallback = callback; + return ScopedAStatus::ok(); +} + +ScopedAStatus RemoteAccessService::clearRemoteTaskCallback() { + std::lock_guard lockGuard(mLock); + mRemoteTaskCallback.reset(); + return ScopedAStatus::ok(); +} + +ScopedAStatus RemoteAccessService::notifyApStateChange(const ApState& newState) { + ClientContext context; + NotifyWakeupRequiredRequest request = {}; + request.set_iswakeuprequired(newState.isWakeupRequired); + NotifyWakeupRequiredResponse response = {}; + Status status = mGrpcStub->NotifyWakeupRequired(&context, request, &response); + if (!status.ok()) { + return rpcStatusToScopedAStatus(status, "Failed to notify isWakeupRequired"); + } + + if (newState.isReadyForRemoteTask) { + maybeStartTaskLoop(); + } else { + maybeStopTaskLoop(); + } + return ScopedAStatus::ok(); +} + +bool RemoteAccessService::checkDumpPermission() { + uid_t uid = AIBinder_getCallingUid(); + return uid == AID_ROOT || uid == AID_SHELL || uid == AID_SYSTEM; +} + +void RemoteAccessService::dumpHelp(int fd) { + dprintf(fd, "%s", + (std::string("RemoteAccess HAL debug interface, Usage: \n") + COMMAND_SET_AP_STATE + + " [0/1](isReadyForRemoteTask) [0/1](isWakeupRequired) Set the new AP state\n" + + COMMAND_START_DEBUG_CALLBACK + + " Start a debug callback that will record the received tasks\n" + + COMMAND_STOP_DEBUG_CALLBACK + " Stop the debug callback\n" + COMMAND_SHOW_TASK + + " Show tasks received by debug callback\n" + COMMAND_GET_DEVICE_ID + + " Get device id\n") + .c_str()); +} + +binder_status_t RemoteAccessService::dump(int fd, const char** args, uint32_t numArgs) { + if (!checkDumpPermission()) { + dprintf(fd, "Caller must be root, system or shell\n"); + return STATUS_PERMISSION_DENIED; + } + + if (numArgs == 0) { + dumpHelp(fd); + return STATUS_OK; + } + + if (!strcmp(args[0], COMMAND_SET_AP_STATE)) { + if (numArgs < 3) { + dumpHelp(fd); + return STATUS_OK; + } + ApState apState = {}; + const char* remoteTaskFlag = args[1]; + if (!strcmp(remoteTaskFlag, "1") && !strcmp(remoteTaskFlag, "0")) { + dumpHelp(fd); + return STATUS_OK; + } + if (!checkBoolFlag(args[1])) { + dumpHelp(fd); + return STATUS_OK; + } + if (!strcmp(args[1], "1")) { + apState.isReadyForRemoteTask = true; + } + if (!checkBoolFlag(args[2])) { + dumpHelp(fd); + return STATUS_OK; + } + if (!strcmp(args[2], "1")) { + apState.isWakeupRequired = true; + } + auto status = notifyApStateChange(apState); + if (!status.isOk()) { + dprintErrorStatus(fd, "Failed to set AP state", status); + } else { + dprintf(fd, "successfully set the new AP state\n"); + } + } else if (!strcmp(args[0], COMMAND_START_DEBUG_CALLBACK)) { + mDebugCallback = ndk::SharedRefBase::make(); + setRemoteTaskCallback(mDebugCallback); + dprintf(fd, "Debug callback registered\n"); + } else if (!strcmp(args[0], COMMAND_STOP_DEBUG_CALLBACK)) { + if (mDebugCallback) { + mDebugCallback.reset(); + } + clearRemoteTaskCallback(); + dprintf(fd, "Debug callback unregistered\n"); + } else if (!strcmp(args[0], COMMAND_SHOW_TASK)) { + if (mDebugCallback) { + dprintf(fd, "%s", mDebugCallback->printTasks().c_str()); + } else { + dprintf(fd, "Debug callback is not currently used, use \"%s\" first.\n", + COMMAND_START_DEBUG_CALLBACK); + } + } else if (!strcmp(args[0], COMMAND_GET_DEVICE_ID)) { + std::string deviceId; + auto status = getDeviceId(&deviceId); + if (!status.isOk()) { + dprintErrorStatus(fd, "Failed to get device ID", status); + } else { + dprintf(fd, "Device Id: %s\n", deviceId.c_str()); + } + } else { + dumpHelp(fd); + } + + return STATUS_OK; +} + +ScopedAStatus DebugRemoteTaskCallback::onRemoteTaskRequested(const std::string& clientId, + const std::vector& data) { + std::lock_guard lockGuard(mLock); + mTasks.push_back({ + .clientId = clientId, + .data = data, + }); + return ScopedAStatus::ok(); +} + +std::string DebugRemoteTaskCallback::printTasks() { + std::lock_guard lockGuard(mLock); + std::string s = StringPrintf("Received %zu tasks in %f seconds", mTasks.size(), + (android::uptimeMillis() - mStartTimeMillis) / 1000.); + for (size_t i = 0; i < mTasks.size(); i++) { + StringAppendF(&s, "Client Id: %s, Data: %s\n", mTasks[i].clientId.c_str(), + printBytes(mTasks[i].data).c_str()); + } + return s; +} + +} // namespace remoteaccess +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/remoteaccess/hal/default/test/Android.bp b/automotive/remoteaccess/hal/default/test/Android.bp new file mode 100644 index 0000000000..227175a611 --- /dev/null +++ b/automotive/remoteaccess/hal/default/test/Android.bp @@ -0,0 +1,47 @@ +// Copyright (C) 2022 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "RemoteAccessServiceUnitTest", + vendor: true, + srcs: ["*.cpp"], + whole_static_libs: [ + "RemoteAccessService", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "liblog", + "libutils", + "libgrpc++", + "libprotobuf-cpp-full", + ], + // libgrpc++.so is installed as root, require root to access it. + require_root: true, + static_libs: [ + "libgtest", + "libgmock", + ], + defaults: [ + "vhalclient_defaults", + ], + cflags: [ + "-Wno-unused-parameter", + ], + test_suites: ["device-tests"], +} diff --git a/automotive/remoteaccess/hal/default/test/RemoteAccessServiceUnitTest.cpp b/automotive/remoteaccess/hal/default/test/RemoteAccessServiceUnitTest.cpp new file mode 100644 index 0000000000..a220aebc5b --- /dev/null +++ b/automotive/remoteaccess/hal/default/test/RemoteAccessServiceUnitTest.cpp @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "RemoteAccessService.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace remoteaccess { + +namespace { + +using ::android::base::ScopedLockAssertion; +using ::android::frameworks::automotive::vhal::AidlHalPropValue; +using ::android::frameworks::automotive::vhal::IHalPropConfig; +using ::android::frameworks::automotive::vhal::IHalPropValue; +using ::android::frameworks::automotive::vhal::ISubscriptionCallback; +using ::android::frameworks::automotive::vhal::ISubscriptionClient; +using ::android::frameworks::automotive::vhal::IVhalClient; + +using ::aidl::android::hardware::automotive::remoteaccess::ApState; +using ::aidl::android::hardware::automotive::remoteaccess::BnRemoteTaskCallback; +using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue; + +using ::grpc::ClientAsyncReaderInterface; +using ::grpc::ClientAsyncResponseReaderInterface; +using ::grpc::ClientContext; +using ::grpc::ClientReader; +using ::grpc::ClientReaderInterface; +using ::grpc::CompletionQueue; +using ::grpc::Status; +using ::grpc::testing::MockClientReader; +using ::ndk::ScopedAStatus; +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgPointee; + +constexpr char kTestVin[] = "test_VIN"; + +} // namespace + +class MockGrpcClientStub : public WakeupClient::StubInterface { + public: + MOCK_METHOD(ClientReaderInterface*, GetRemoteTasksRaw, + (ClientContext * context, const GetRemoteTasksRequest& request)); + MOCK_METHOD(Status, NotifyWakeupRequired, + (ClientContext * context, const NotifyWakeupRequiredRequest& request, + NotifyWakeupRequiredResponse* response)); + // Async methods which we do not care. + MOCK_METHOD(ClientAsyncReaderInterface*, AsyncGetRemoteTasksRaw, + (ClientContext * context, const GetRemoteTasksRequest& request, CompletionQueue* cq, + void* tag)); + MOCK_METHOD(ClientAsyncReaderInterface*, PrepareAsyncGetRemoteTasksRaw, + (ClientContext * context, const GetRemoteTasksRequest& request, + CompletionQueue* cq)); + MOCK_METHOD(ClientAsyncResponseReaderInterface*, + AsyncNotifyWakeupRequiredRaw, + (ClientContext * context, const NotifyWakeupRequiredRequest& request, + CompletionQueue* cq)); + MOCK_METHOD(ClientAsyncResponseReaderInterface*, + PrepareAsyncNotifyWakeupRequiredRaw, + (ClientContext * context, const NotifyWakeupRequiredRequest& request, + CompletionQueue* cq)); +}; + +class FakeVhalClient final : public android::frameworks::automotive::vhal::IVhalClient { + public: + template + using VhalClientResult = android::hardware::automotive::vehicle::VhalResult; + + inline bool isAidlVhal() { return true; } + + VhalClientResult> getValueSync( + const IHalPropValue& requestValue) override { + auto propValue = std::make_unique(requestValue.getPropId()); + propValue->setStringValue(kTestVin); + return propValue; + } + + std::unique_ptr createHalPropValue(int32_t propId) override { + return std::make_unique(propId); + } + + // Functions we do not care. + std::unique_ptr createHalPropValue([[maybe_unused]] int32_t propId, + [[maybe_unused]] int32_t areaId) override { + return nullptr; + } + + void getValue([[maybe_unused]] const IHalPropValue& requestValue, + [[maybe_unused]] std::shared_ptr callback) override {} + + void setValue([[maybe_unused]] const IHalPropValue& requestValue, + [[maybe_unused]] std::shared_ptr callback) override {} + + VhalClientResult setValueSync([[maybe_unused]] const IHalPropValue& requestValue) { + return {}; + } + + VhalClientResult addOnBinderDiedCallback( + [[maybe_unused]] std::shared_ptr callback) override { + return {}; + } + + VhalClientResult removeOnBinderDiedCallback( + [[maybe_unused]] std::shared_ptr callback) override { + return {}; + } + + VhalClientResult>> getAllPropConfigs() override { + return std::vector>(); + } + + VhalClientResult>> getPropConfigs( + [[maybe_unused]] std::vector propIds) override { + return std::vector>(); + } + + std::unique_ptr getSubscriptionClient( + [[maybe_unused]] std::shared_ptr callback) override { + return nullptr; + } +}; + +class FakeRemoteTaskCallback : public BnRemoteTaskCallback { + public: + ScopedAStatus onRemoteTaskRequested(const std::string& clientId, + const std::vector& data) override { + std::lock_guard lockGuard(mLock); + mDataByClientId[clientId] = data; + mTaskCount++; + mCv.notify_all(); + return ScopedAStatus::ok(); + } + + std::vector getData(const std::string& clientId) { return mDataByClientId[clientId]; } + + bool wait(size_t taskCount, size_t timeoutInSec) { + std::unique_lock lock(mLock); + return mCv.wait_for(lock, std::chrono::seconds(timeoutInSec), [taskCount, this] { + ScopedLockAssertion lockAssertion(mLock); + return mTaskCount >= taskCount; + }); + } + + private: + std::mutex mLock; + std::unordered_map> mDataByClientId GUARDED_BY(mLock); + size_t mTaskCount GUARDED_BY(mLock) = 0; + std::condition_variable mCv; +}; + +class RemoteAccessServiceUnitTest : public ::testing::Test { + public: + virtual void SetUp() override { + mGrpcWakeupClientStub = std::make_unique(); + mService = ndk::SharedRefBase::make(mGrpcWakeupClientStub.get()); + } + + MockGrpcClientStub* getGrpcWakeupClientStub() { return mGrpcWakeupClientStub.get(); } + + RemoteAccessService* getService() { return mService.get(); } + + void setRetryWaitInMs(size_t retryWaitInMs) { mService->setRetryWaitInMs(retryWaitInMs); } + + ScopedAStatus getDeviceIdWithClient(IVhalClient& vhalClient, std::string* deviceId) { + return mService->getDeviceIdWithClient(vhalClient, deviceId); + } + + private: + std::unique_ptr mGrpcWakeupClientStub; + std::shared_ptr mService; +}; + +TEST_F(RemoteAccessServiceUnitTest, TestGetWakeupServiceName) { + std::string serviceName; + + ScopedAStatus status = getService()->getWakeupServiceName(&serviceName); + + EXPECT_TRUE(status.isOk()); + EXPECT_EQ(serviceName, "com.google.vehicle.wakeup"); +} + +TEST_F(RemoteAccessServiceUnitTest, TestNotifyApStateChangeWakeupRequired) { + bool isWakeupRequired = false; + EXPECT_CALL(*getGrpcWakeupClientStub(), NotifyWakeupRequired) + .WillOnce([&isWakeupRequired]([[maybe_unused]] ClientContext* context, + const NotifyWakeupRequiredRequest& request, + [[maybe_unused]] NotifyWakeupRequiredResponse* response) { + isWakeupRequired = request.iswakeuprequired(); + return Status(); + }); + + ApState newState = { + .isWakeupRequired = true, + }; + ScopedAStatus status = getService()->notifyApStateChange(newState); + + EXPECT_TRUE(status.isOk()); + EXPECT_TRUE(isWakeupRequired); +} + +TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasks) { + GetRemoteTasksResponse response1; + std::vector testData = {0xde, 0xad, 0xbe, 0xef}; + response1.set_clientid("1"); + response1.set_data(testData.data(), testData.size()); + GetRemoteTasksResponse response2; + response2.set_clientid("2"); + std::shared_ptr callback = + ndk::SharedRefBase::make(); + + ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw) + .WillByDefault( + [response1, response2]([[maybe_unused]] ClientContext* context, + [[maybe_unused]] const GetRemoteTasksRequest& request) { + // mockReader ownership will be transferred to the client so we don't own it + // here. + MockClientReader* mockClientReader = + new MockClientReader(); + EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK)); + EXPECT_CALL(*mockClientReader, Read(_)) + .WillOnce(DoAll(SetArgPointee<0>(response1), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(response2), Return(true))) + .WillRepeatedly(Return(false)); + return mockClientReader; + }); + + getService()->setRemoteTaskCallback(callback); + // Start the long live connection to receive tasks. + ApState newState = { + .isReadyForRemoteTask = true, + }; + ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk()); + + ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10)) + << "Did not receive enough tasks"; + EXPECT_EQ(callback->getData("1"), testData); + EXPECT_EQ(callback->getData("2"), std::vector()); +} + +TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksRetryConnection) { + GetRemoteTasksResponse response; + std::shared_ptr callback = + ndk::SharedRefBase::make(); + + ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw) + .WillByDefault([response]([[maybe_unused]] ClientContext* context, + [[maybe_unused]] const GetRemoteTasksRequest& request) { + // mockReader ownership will be transferred to the client so we don't own it here. + MockClientReader* mockClientReader = + new MockClientReader(); + EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK)); + // Connection fails after receiving one task. Should retry after some time. + EXPECT_CALL(*mockClientReader, Read(_)) + .WillOnce(DoAll(SetArgPointee<0>(response), Return(true))) + .WillRepeatedly(Return(false)); + return mockClientReader; + }); + + getService()->setRemoteTaskCallback(callback); + setRetryWaitInMs(100); + // Start the long live connection to receive tasks. + ApState newState = { + .isReadyForRemoteTask = true, + }; + ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk()); + + ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10)) + << "Did not receive enough tasks"; +} + +TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksDefaultNotReady) { + GetRemoteTasksResponse response1; + std::vector testData = {0xde, 0xad, 0xbe, 0xef}; + response1.set_clientid("1"); + response1.set_data(testData.data(), testData.size()); + GetRemoteTasksResponse response2; + response2.set_clientid("2"); + std::shared_ptr callback = + ndk::SharedRefBase::make(); + + EXPECT_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw).Times(0); + + // Default state is not ready for remote tasks, so no callback will be called. + getService()->setRemoteTaskCallback(callback); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} + +TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksNotReadyAfterReady) { + GetRemoteTasksResponse response1; + std::vector testData = {0xde, 0xad, 0xbe, 0xef}; + response1.set_clientid("1"); + response1.set_data(testData.data(), testData.size()); + GetRemoteTasksResponse response2; + response2.set_clientid("2"); + std::shared_ptr callback = + ndk::SharedRefBase::make(); + + ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw) + .WillByDefault( + [response1, response2]([[maybe_unused]] ClientContext* context, + [[maybe_unused]] const GetRemoteTasksRequest& request) { + // mockReader ownership will be transferred to the client so we don't own it + // here. + MockClientReader* mockClientReader = + new MockClientReader(); + EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK)); + EXPECT_CALL(*mockClientReader, Read(_)) + .WillOnce(DoAll(SetArgPointee<0>(response1), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(response2), Return(true))) + .WillRepeatedly(Return(false)); + return mockClientReader; + }); + // Should only be called once when is is ready for remote task. + EXPECT_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw).Times(1); + + getService()->setRemoteTaskCallback(callback); + setRetryWaitInMs(100); + // Start the long live connection to receive tasks. + ApState newState = { + .isReadyForRemoteTask = true, + }; + ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk()); + ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10)) + << "Did not receive enough tasks"; + + // Stop the long live connection. + newState.isReadyForRemoteTask = false; + ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk()); + + // Wait for the retry delay, but the loop should already exit. + std::this_thread::sleep_for(std::chrono::milliseconds(150)); +} + +TEST_F(RemoteAccessServiceUnitTest, testGetDeviceId) { + std::string deviceId; + + FakeVhalClient vhalClient; + + ASSERT_TRUE(getDeviceIdWithClient(vhalClient, &deviceId).isOk()); + ASSERT_EQ(deviceId, kTestVin); +} + +} // namespace remoteaccess +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/remoteaccess/test_grpc_server/README.md b/automotive/remoteaccess/test_grpc_server/README.md new file mode 100644 index 0000000000..090a8fdfc1 --- /dev/null +++ b/automotive/remoteaccess/test_grpc_server/README.md @@ -0,0 +1,282 @@ +# Test GRPC Server. + +A test GRPC server that implements wakeup_client.proto. This test server acts +as a reference implementation for a remote wakeup client running on TCU. The +test server does not communicate with any actual network server. It has the +following behavior: + +* It starts a GRPC server on 'DGRPC_SERVICE_ADDRESS' compile flag which is + localhost:50051. The GRPC server provides the service according to + hardware/interfaces/automotive/remoteaccess/hal/default/proto/wakeup_client.proto. + + In real implementation, DGRPC_SERVICE_ADDRESS can be specified to any IP + address where the TCU can be exposed to Application Processor. The default + remote access HAL implementation + (hardware/interfaces/automotive/remoteaccess/hal/default/Android.bp) also + uses DGRPC_SERVICE_ADDRESS to find this GRPC server, so it must have the + same IP address. + +* It generates a fake task using FakeTaskGenerator every 'kTaskIntervalInMs' ms. + + In real implementation, it should receive task from the remote server. + +* Each fake task has an increasing unique client ID. The task data is always + what's defined for 'DATA' variable. + + In real implementation, the client ID and task data should come from the + remote server. + +* The generated tasks are put into a task queue which is a priority queue sorted + by task received time. + + In real implementation, if the server provides a task timestamp, then this + queue can be sorted by that task timestamp instead. + +* When the Application processor is started, the remote access HAL running on + Android will call 'GetRemoteTasks' to establish a long-live connection. This + connection is used to deliver all task data from remote wakeup client to + remote access HAL, which eventually to car service and applications. + + When the 'GetRemoteTasks' is called, the wakeup client must send all the + pending tasks through the 'ServerWriter'. If no task is pending, then it must + block and wait for a new task to arrive. + + If one task data fails to be sent through the channel, it likely means + the other side (Application processor) is shutting down or has closed the + channel. The wakeup client must put the task back to the pending queue and + wait for a new 'GetRemoteTasks' request to retry sending the task. + +* When a new task arrives, if 'WakeupRequired' is true, then try to wakeup + the Application Processor by sending a specific CAN message. It is possible that + the waking up is already in progress. This is okay since Vehicle Processor + should ignore wakeup message if a wakeup is already in progress. + +* When 'WakeupRequired' is updated from false to true, if there are unexpired + pending tasks in the task queue, try to wakeup Application Processor. + + This is to handle the situation when a task arrives while the device is + shutting down. During the device shutdown, the channel to deliver the remote + tasks to Application Processor is shutdown so the new task will be added to the + task queue. 'WakeupRequired' will be set to false to prevent the wakeup + message preventing the shutdown. After the shutdown is complete, + 'WakeupRequired' will be set to true and this wakeup client must try to wake + up the device again to execute the pending tasks. + +* Every pending task has a timeout: 'KTaskTimeoutInMs'. If the pending task + is not delivered to remote access HAL before the timeout (through + GetRemoteTasks), the task timed out and a warning message is logged. + + In real implementation, this kTaskTimeoutInMs has to be set long enough to + allow an Android bootup to happen. 20s is a reasonable value. When a task + timed out, the wakeup client should also report to remote task server about + the task timeout failure. + +## How to build the test wakeup client + +* Under android root: `make -j TestWakeupClientServer` + +## How to push the test wakeup client to a TCU which runs Android. + +* Make the target device writable: + + `adb root` + + `adb remount` + + `adb reboot` + + `adb root` + + `adb remount` + +* Under android root: `cd $ANDROID_PRODUCT_OUT` + +* `adb push vendor/bin/TestWakeupClientServer /vendor/bin` + +* `adb shell` + +* `su` + +* `/vendor/bin/TestWakeupClientServer` + +## How to build and test the test wakeup client using one car emulator. + +In this test setup we will use one google car emulator +(sdk_car_x86_64-userdebug). We assume both the TCU and the remote access HAL +runs on the same Android system, and they communicate through local loopback +interface. + +* Under android root, `source build/envsetup.sh` + +* `lunch sdk_car_x86_64-userdebug` + +* `m -j` + +* Run the emulator, the '-read-only' flag is required to run multiple instances: + + `emulator -writable-system -read-only` + +* The android lunch target: sdk_car_x86_64-userdebug and + cf_x86_64_auto-userdebug already contains the default remote access HAL. For + other lunch target, you can add the default remote access HAL by adding + 'android.hardware.automotive.remoteaccess@V1-default-service' to + 'PRODUCT_PACKAGES' variable in mk file, see `device/generic/car/common/car.mk` + as example. + + To verify whether remote access HAL is running, you can use the following + command to check: + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default` + +* Make the target device writable: + + `adb root` + + `adb remount` + + `adb reboot` + + `adb root` + + `adb remount` + +* `make -j TestWakeupClientServer` + +* `adb push $ANDROID_PRODUCT_OUT/vendor/bin/TestWakeupClientServer /vendor/bin` + +* `adb shell` + +* `su` + +* `/vendor/bin/TestWakeupClientServer` + +* Remote access HAL should start by default when the car emulator starts. Now + the test wake up client should also be running and generating fake tasks. + + Start a new adb shell session by + + `adb shell` + + `su` + +* Issue the command to start a simple debug callback that will capture all the + received tasks at the remote access HAL side: + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --start-debug-callback` + +* Issue the following debug command to remote access HAL to establish the + communication channel between it and the test wakeup client. This command + also notifies that wakeup is not required: + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --set-ap-state 1 0` + +* Wait for a while, issue the following command to show the received fake tasks: + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --show-task` + + You should expect to see some received tasks printed out. + +* Simulate the Application Processor is shutting down by issuing the following + command: + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --set-ap-state 0 0` + +* Wait for a while, issue the following command to show received tasks again: + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --show-task` + + You should expect to see no new tasks received since remote access HAL already + closed the communication channel. + +* Simulate the Application Processor is already shutdown and wake up is required + now: + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --set-ap-state 0 1` + + Now you should expect to see the test wakeup client printing out messages + that it is trying to wake up application processor. + +* Simulate the Application Processor is waken up: + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --set-ap-state 1 0` + +* A new communication channel should have been established and all pending + non-expired tasks should be delivered to the remote access HAL. + + `dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --show-task` + +* Now you can issue `ctrl c` on the first adb shell to stop the test wakeup + client. + +## How to build and test the test wakeup client using two car emulators. + +In this test case, we are going to use two car emulators, one as the +Application Processor, one as the TCU. + +* Change the IP address to allow IP communication between different emulator + instances. For detail about why we change it this way, see [interconnecting + emulator instance](https://developer.android.com/studio/run/emulator-networking#connecting). + + Change 'DGRPC_SERVICE_ADDRESS' in `test_grpc_server/Android.bp` to + `10.0.2.15:50051`. + + Change `DGRPC_SERVICE_ADDRESS` in 'hal/defaut/Android.bp' to + `10.0.2.2:50051`. + +* Under android root: `source build/envsetup.sh` + +* `lunch sdk_car_x86_64-userdebug` + +* `m -j` + +* Start one car emulator as TCU + + `emulator -writable-system -read-only` + +* Start a new shell session. Connect to the emulator's console, + see [Start and stop a console session](https://developer.android.com/studio/run/emulator-console#console-session) + for detail. + + `telnet localhost 5554` + +* `auth auth_token` where auth_token must match the contents of the + `.emulator_console_auth_token` file. + +* `redir add tcp:50051:50051` + +* Exit the telnet session + + Make the target device writable: + + `adb root` + + `adb remount` + + `adb reboot` + + `adb root` + + `adb remount` + +* `make -j TestWakeupClientServer` + +* `adb push $ANDROID_PRODUCT_OUT/vendor/bin/TestWakeupClientServer /vendor/bin` + +* `adb shell` + +* `su` + +* `/vendor/bin/TestWakeupClientServer` + +* Start a new shell, start another car emulator as the Application Processor: + + `emulator -writable-system -read-only` + +* Connect to adb shell for the application processor: + + `adb -s emulator-5556 shell` + + `su` + +* Follow the test instructions for one car emulator using the 'dumpsys' + commands. diff --git a/automotive/remoteaccess/test_grpc_server/impl/Android.bp b/automotive/remoteaccess/test_grpc_server/impl/Android.bp new file mode 100644 index 0000000000..e978c8caa4 --- /dev/null +++ b/automotive/remoteaccess/test_grpc_server/impl/Android.bp @@ -0,0 +1,42 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_binary { + name: "TestWakeupClientServer", + vendor: true, + srcs: ["src/*.cpp"], + local_include_dirs: ["include"], + shared_libs: [ + "libbase", + "libutils", + "libgrpc++", + "libprotobuf-cpp-full", + ], + whole_static_libs: [ + "wakeup_client_protos", + ], + cflags: [ + "-Wno-unused-parameter", + "-DGRPC_SERVICE_ADDRESS=\"localhost:50051\"", + ], +} diff --git a/automotive/remoteaccess/test_grpc_server/impl/include/TestWakeupClientServiceImpl.h b/automotive/remoteaccess/test_grpc_server/impl/include/TestWakeupClientServiceImpl.h new file mode 100644 index 0000000000..12bd93b34c --- /dev/null +++ b/automotive/remoteaccess/test_grpc_server/impl/include/TestWakeupClientServiceImpl.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace remoteaccess { + +// A class to generate fake task for testing. Not required for real implementation. In real +// implementation, the task should come from remote task server. This class is thread-safe. +class FakeTaskGenerator final { + public: + GetRemoteTasksResponse generateTask(); + + private: + // Simulates the client ID for each task. + std::atomic mCurrentClientId = 0; + constexpr static uint8_t DATA[] = {0xde, 0xad, 0xbe, 0xef}; +}; + +struct TaskInfo { + // This is unique per-task. Note that a task might be popped and put back into the task queue, + // it will have a new task ID but the same clientId in the task data. + int taskId; + int64_t timestampInMs; + GetRemoteTasksResponse taskData; +}; + +struct TaskInfoComparator { + // We want the smallest timestamp and smallest task ID on top. + bool operator()(const TaskInfo& l, const TaskInfo& r) { + return l.timestampInMs > r.timestampInMs || + (l.timestampInMs == r.timestampInMs && l.taskId > r.taskId); + } +}; + +// forward-declaration. +class TaskQueue; + +class TaskTimeoutMessageHandler final : public android::MessageHandler { + public: + TaskTimeoutMessageHandler(TaskQueue* taskQueue); + void handleMessage(const android::Message& message) override; + + private: + TaskQueue* mTaskQueue; +}; + +// TaskQueue is thread-safe. +class TaskQueue final { + public: + TaskQueue(); + ~TaskQueue(); + + void add(const GetRemoteTasksResponse& response); + std::optional maybePopOne(); + void waitForTask(); + void stopWait(); + void handleTaskTimeout(); + bool isEmpty(); + + private: + std::thread mCheckTaskTimeoutThread; + std::mutex mLock; + std::priority_queue, TaskInfoComparator> mTasks + GUARDED_BY(mLock); + // A variable to notify mTasks is not empty. + std::condition_variable mTasksNotEmptyCv; + bool mStopped GUARDED_BY(mLock); + android::sp mLooper; + android::sp mTaskTimeoutMessageHandler; + std::atomic mTaskIdCounter = 0; + + void checkForTestTimeoutLoop(); + void waitForTaskWithLock(std::unique_lock& lock); +}; + +class TestWakeupClientServiceImpl final : public WakeupClient::Service { + public: + TestWakeupClientServiceImpl(); + + ~TestWakeupClientServiceImpl(); + + grpc::Status GetRemoteTasks(grpc::ServerContext* context, const GetRemoteTasksRequest* request, + grpc::ServerWriter* writer) override; + + grpc::Status NotifyWakeupRequired(grpc::ServerContext* context, + const NotifyWakeupRequiredRequest* request, + NotifyWakeupRequiredResponse* response) override; + + private: + // This is a thread for communicating with remote wakeup server (via network) and receive tasks + // from it. + std::thread mThread; + // A variable to notify server is stopping. + std::condition_variable mServerStoppedCv; + // Whether wakeup AP is required for executing tasks. + std::atomic mWakeupRequired = false; + std::mutex mLock; + bool mServerStopped GUARDED_BY(mLock); + + // Thread-safe. For test impl only. + FakeTaskGenerator mFakeTaskGenerator; + // Thread-sfae. + TaskQueue mTaskQueue; + + void fakeTaskGenerateLoop(); + + void wakeupApplicationProcessor(); +}; + +} // namespace remoteaccess +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/remoteaccess/test_grpc_server/impl/src/TestWakeupClientServiceImpl.cpp b/automotive/remoteaccess/test_grpc_server/impl/src/TestWakeupClientServiceImpl.cpp new file mode 100644 index 0000000000..795265fb64 --- /dev/null +++ b/automotive/remoteaccess/test_grpc_server/impl/src/TestWakeupClientServiceImpl.cpp @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "TestWakeupClientServiceImpl.h" + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace remoteaccess { + +namespace { + +using ::android::uptimeMillis; +using ::android::base::ScopedLockAssertion; +using ::android::base::StringPrintf; +using ::grpc::ServerContext; +using ::grpc::ServerWriter; +using ::grpc::Status; + +constexpr int kTaskIntervalInMs = 5'000; +constexpr int64_t KTaskTimeoutInMs = 20'000; + +} // namespace + +GetRemoteTasksResponse FakeTaskGenerator::generateTask() { + int clientId = mCurrentClientId++; + GetRemoteTasksResponse response; + response.set_data(std::string(reinterpret_cast(DATA), sizeof(DATA))); + std::string clientIdStr = StringPrintf("%d", clientId); + response.set_clientid(clientIdStr); + return response; +} + +TaskTimeoutMessageHandler::TaskTimeoutMessageHandler(TaskQueue* taskQueue) + : mTaskQueue(taskQueue) {} + +void TaskTimeoutMessageHandler::handleMessage(const android::Message& message) { + mTaskQueue->handleTaskTimeout(); +} + +TaskQueue::TaskQueue() { + mTaskTimeoutMessageHandler = android::sp::make(this); + mLooper = Looper::prepare(/*opts=*/0); + mCheckTaskTimeoutThread = std::thread([this] { checkForTestTimeoutLoop(); }); +} + +TaskQueue::~TaskQueue() { + { + std::lock_guard lockGuard(mLock); + mStopped = true; + } + while (true) { + // Remove all pending timeout handlers from queue. + if (!maybePopOne().has_value()) { + break; + } + } + if (mCheckTaskTimeoutThread.joinable()) { + mCheckTaskTimeoutThread.join(); + } +} + +std::optional TaskQueue::maybePopOne() { + std::lock_guard lockGuard(mLock); + if (mTasks.size() == 0) { + return std::nullopt; + } + TaskInfo response = std::move(mTasks.top()); + mTasks.pop(); + mLooper->removeMessages(mTaskTimeoutMessageHandler, response.taskId); + return std::move(response.taskData); +} + +void TaskQueue::add(const GetRemoteTasksResponse& task) { + std::lock_guard lockGuard(mLock); + if (mStopped) { + return; + } + int taskId = mTaskIdCounter++; + mTasks.push(TaskInfo{ + .taskId = taskId, + .timestampInMs = uptimeMillis(), + .taskData = task, + }); + android::Message message(taskId); + mLooper->sendMessageDelayed(KTaskTimeoutInMs * 1000, mTaskTimeoutMessageHandler, message); + mTasksNotEmptyCv.notify_all(); +} + +void TaskQueue::waitForTask() { + std::unique_lock lock(mLock); + waitForTaskWithLock(lock); +} + +void TaskQueue::waitForTaskWithLock(std::unique_lock& lock) { + mTasksNotEmptyCv.wait(lock, [this] { + ScopedLockAssertion lockAssertion(mLock); + return mTasks.size() > 0 || mStopped; + }); +} + +void TaskQueue::stopWait() { + std::lock_guard lockGuard(mLock); + mStopped = true; + mTasksNotEmptyCv.notify_all(); +} + +bool TaskQueue::isEmpty() { + std::lock_guard lockGuard(mLock); + return mTasks.size() == 0 || mStopped; +} + +void TaskQueue::checkForTestTimeoutLoop() { + Looper::setForThread(mLooper); + + while (true) { + { + std::unique_lock lock(mLock); + if (mStopped) { + return; + } + } + + mLooper->pollAll(/*timeoutMillis=*/-1); + } +} + +void TaskQueue::handleTaskTimeout() { + // We know which task timed-out from the taskId in the message. However, there is no easy way + // to remove a specific task with the task ID from the priority_queue, so we just check from + // the top of the queue (which have the oldest tasks). + std::lock_guard lockGuard(mLock); + int64_t now = uptimeMillis(); + while (mTasks.size() > 0) { + const TaskInfo& taskInfo = mTasks.top(); + if (taskInfo.timestampInMs + KTaskTimeoutInMs > now) { + break; + } + // In real implementation, this should report task failure to remote wakeup server. + printf("Task for client ID: %s timed-out, added at %" PRId64 " ms, now %" PRId64 " ms", + taskInfo.taskData.clientid().c_str(), taskInfo.timestampInMs, now); + mTasks.pop(); + } +} + +TestWakeupClientServiceImpl::TestWakeupClientServiceImpl() { + mThread = std::thread([this] { fakeTaskGenerateLoop(); }); +} + +TestWakeupClientServiceImpl::~TestWakeupClientServiceImpl() { + { + std::lock_guard lockGuard(mLock); + mServerStopped = true; + mServerStoppedCv.notify_all(); + } + mTaskQueue.stopWait(); + if (mThread.joinable()) { + mThread.join(); + } +} + +void TestWakeupClientServiceImpl::fakeTaskGenerateLoop() { + // In actual implementation, this should communicate with the remote server and receives tasks + // from it. Here we simulate receiving one remote task every {kTaskIntervalInMs}ms. + while (true) { + mTaskQueue.add(mFakeTaskGenerator.generateTask()); + printf("Received a new task\n"); + if (mWakeupRequired) { + wakeupApplicationProcessor(); + } + + printf("Sleeping for %d seconds until next task\n", kTaskIntervalInMs); + + std::unique_lock lk(mLock); + if (mServerStoppedCv.wait_for(lk, std::chrono::milliseconds(kTaskIntervalInMs), [this] { + ScopedLockAssertion lockAssertion(mLock); + return mServerStopped; + })) { + // If the stopped flag is set, we are quitting, exit the loop. + return; + } + } +} + +Status TestWakeupClientServiceImpl::GetRemoteTasks(ServerContext* context, + const GetRemoteTasksRequest* request, + ServerWriter* writer) { + printf("GetRemoteTasks called\n"); + while (true) { + mTaskQueue.waitForTask(); + + while (true) { + auto maybeTask = mTaskQueue.maybePopOne(); + if (!maybeTask.has_value()) { + // No task left, loop again and wait for another task(s). + break; + } + // Loop through all the task in the queue but obtain lock for each element so we don't + // hold lock while writing the response. + const GetRemoteTasksResponse& response = maybeTask.value(); + if (!writer->Write(response)) { + // Broken stream, maybe the client is shutting down. + printf("Failed to deliver remote task to remote access HAL\n"); + // The task failed to be sent, add it back to the queue. The order might change, but + // it is okay. + mTaskQueue.add(response); + return Status::CANCELLED; + } + } + } + return Status::OK; +} + +Status TestWakeupClientServiceImpl::NotifyWakeupRequired(ServerContext* context, + const NotifyWakeupRequiredRequest* request, + NotifyWakeupRequiredResponse* response) { + if (request->iswakeuprequired() && !mWakeupRequired && !mTaskQueue.isEmpty()) { + // If wakeup is now required and previously not required, this means we have finished + // shutting down the device. If there are still pending tasks, try waking up AP again + // to finish executing those tasks. + wakeupApplicationProcessor(); + } + mWakeupRequired = request->iswakeuprequired(); + return Status::OK; +} + +void TestWakeupClientServiceImpl::wakeupApplicationProcessor() { + printf("Waking up application processor...\n"); + // TODO(b/254547153): Send can bus message using socket CAN once we know what the message is. +} + +} // namespace remoteaccess +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/remoteaccess/test_grpc_server/impl/src/main.cpp b/automotive/remoteaccess/test_grpc_server/impl/src/main.cpp new file mode 100644 index 0000000000..52698b5761 --- /dev/null +++ b/automotive/remoteaccess/test_grpc_server/impl/src/main.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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. + */ + +#include + +#include "TestWakeupClientServiceImpl.h" + +#include +#include +#include +#include + +using ::android::hardware::automotive::remoteaccess::TestWakeupClientServiceImpl; +using ::grpc::Server; +using ::grpc::ServerBuilder; +using ::grpc::ServerWriter; + +void RunServer() { + std::string serverAddress(GRPC_SERVICE_ADDRESS); + std::shared_ptr service = + std::make_unique(); + + ServerBuilder builder; + builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials()); + builder.RegisterService(service.get()); + std::unique_ptr server(builder.BuildAndStart()); + printf("Test Remote Access GRPC Server listening on %s\n", serverAddress.c_str()); + server->Wait(); +} + +int main(int argc, char** argv) { + RunServer(); + return 0; +} diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index d4be2bf860..53b9c7c5ec 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -112,6 +112,13 @@ .* + + android.hardware.automotive.remoteaccess + + IRemoteAccess + .* + + android.hardware.automotive.vehicle 2.0