diff --git a/compatibility_matrices/exclude/fcm_exclude.cpp b/compatibility_matrices/exclude/fcm_exclude.cpp index b17c0e22c6..f3374c3835 100644 --- a/compatibility_matrices/exclude/fcm_exclude.cpp +++ b/compatibility_matrices/exclude/fcm_exclude.cpp @@ -64,6 +64,7 @@ bool ShouldCheckMissingHalsInFcm(const std::string& package) { "android.hardware.keymaster", "android.hardware.media.bufferpool2", "android.hardware.radio", + "android.hardware.threadnetwork", "android.hardware.uwb.fira_android", // Fastboot HAL is only used by recovery. Recovery is owned by OEM. Framework diff --git a/staging/threadnetwork/aidl/Android.bp b/staging/threadnetwork/aidl/Android.bp new file mode 100644 index 0000000000..fcd3ab8501 --- /dev/null +++ b/staging/threadnetwork/aidl/Android.bp @@ -0,0 +1,17 @@ +aidl_interface { + name: "android.hardware.threadnetwork", + host_supported: true, + vendor_available: true, + + srcs: [ + "android/hardware/threadnetwork/*.aidl", + ], + + unstable: true, + + backend: { + ndk: { + enabled: true, + }, + }, +} diff --git a/staging/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChip.aidl b/staging/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChip.aidl new file mode 100644 index 0000000000..3c57149324 --- /dev/null +++ b/staging/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChip.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.threadnetwork; + +import android.hardware.threadnetwork.IThreadChipCallback; + +/** + * Controls a Thread radio chip on the device. + */ + +interface IThreadChip { + /** + * The operation failed for the internal error. + */ + const int ERROR_FAILED = 1; + + /** + * Insufficient buffers available to send frames. + */ + const int ERROR_NO_BUFS = 2; + + /** + * Service is busy and could not service the operation. + */ + const int ERROR_BUSY = 3; + + /** + * This method initializes the Thread HAL instance. If open completes + * successfully, then the Thread HAL instance is ready to accept spinel + * messages through sendSpinelFrame() API. + * + * @param callback A IThreadChipCallback callback instance. If multiple + * callbacks are passed in, the open() will return ERROR_BUSY. + * + * @throws EX_ILLEGAL_ARGUMENT if the callback handle is invalid (for example, it is null). + * @throws ServiceSpecificException with one of the following values: + * - ERROR_FAILED The interface cannot be opened due to an internal error. + * - ERROR_BUSY This interface is in use. + */ + void open(in IThreadChipCallback callback); + + /** + * Close the Thread HAL instance. Must free all resources. + * + * @throws EX_ILLEGAL_STATE if the Thread HAL instance is not opened. + * + */ + void close(); + + /** + * This method resets the Thread HAL internal state. The callback registered by + * `open()` won’t be reset and the resource allocated by `open()` won’t be free. + * + */ + void reset(); + + /** + * This method sends a spinel frame to the Thread HAL. + * + * This method should block until the frame is sent out successfully or + * the method throws errors immediately. + * + * Spinel Protocol: + * https://github.com/openthread/openthread/blob/main/src/lib/spinel/spinel.h + * + * @param frame The spinel frame to be sent. + * + * @throws ServiceSpecificException with one of the following values: + * - ERROR_FAILED The Thread HAL failed to send the frame for an internal reason. + * - ERROR_NO_BUFS Insufficient buffer space to send the frame. + * - ERROR_BUSY The Thread HAL is busy. + */ + void sendSpinelFrame(in byte[] frame); +} diff --git a/staging/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChipCallback.aidl b/staging/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChipCallback.aidl new file mode 100644 index 0000000000..a0fe88cf8d --- /dev/null +++ b/staging/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChipCallback.aidl @@ -0,0 +1,30 @@ +/* + * 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.threadnetwork; + +interface IThreadChipCallback { + /** + * This method is called when a spinel frame is received. Thread network + * will process the received spinel frame. + * + * Spinel Protocol: + * https://github.com/openthread/openthread/blob/main/src/lib/spinel/spinel.h + * + * @param frame The received spinel frame. + */ + oneway void onReceiveSpinelFrame(in byte[] frame); +} diff --git a/staging/threadnetwork/aidl/default/Android.bp b/staging/threadnetwork/aidl/default/Android.bp new file mode 100644 index 0000000000..c7012951ea --- /dev/null +++ b/staging/threadnetwork/aidl/default/Android.bp @@ -0,0 +1,54 @@ +// 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. + +cc_defaults { + name: "threadnetwork_service_default", + vendor: true, + relative_install_path: "hw", + + shared_libs: [ + "android.hardware.threadnetwork-ndk", + "libbase", + "libbinder_ndk", + "libcutils", + "liblog", + "libutils", + ], + + static_libs: [ + "openthread-common", + "openthread-hdlc", + "openthread-platform", + "openthread-posix", + "openthread-url", + ], + + srcs: [ + "main.cpp", + "service.cpp", + "thread_chip.cpp", + "utils.cpp", + ], +} + +cc_binary { + name: "android.hardware.threadnetwork-service.sim", + defaults: ["threadnetwork_service_default"], + init_rc: ["android.hardware.threadnetwork-service.sim.rc"], +} + +cc_binary { + name: "android.hardware.threadnetwork-service", + defaults: ["threadnetwork_service_default"], +} diff --git a/staging/threadnetwork/aidl/default/android.hardware.threadnetwork-service.sim.rc b/staging/threadnetwork/aidl/default/android.hardware.threadnetwork-service.sim.rc new file mode 100644 index 0000000000..2fb409cd93 --- /dev/null +++ b/staging/threadnetwork/aidl/default/android.hardware.threadnetwork-service.sim.rc @@ -0,0 +1,3 @@ +service vendor.threadnetwork_hal /vendor/bin/hw/android.hardware.threadnetwork-service.sim spinel+hdlc+forkpty:///vendor/bin/ot-rcp?forkpty-arg=1 + class hal + user thread_network diff --git a/staging/threadnetwork/aidl/default/main.cpp b/staging/threadnetwork/aidl/default/main.cpp new file mode 100644 index 0000000000..b6c8bbb7ba --- /dev/null +++ b/staging/threadnetwork/aidl/default/main.cpp @@ -0,0 +1,30 @@ +/* + * 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 "service.hpp" + +int main(int argc, char* argv[]) { + CHECK_GT(argc, 1); + aidl::android::hardware::threadnetwork::Service service(&argv[1], argc - 1); + + ALOGI("Thread Network HAL is running"); + + service.startLoop(); + return EXIT_FAILURE; // should not reach +} diff --git a/staging/threadnetwork/aidl/default/service.cpp b/staging/threadnetwork/aidl/default/service.cpp new file mode 100644 index 0000000000..8047214f24 --- /dev/null +++ b/staging/threadnetwork/aidl/default/service.cpp @@ -0,0 +1,90 @@ +/* + * 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 "service.hpp" + +#include +#include +#include +#include + +#include "thread_chip.hpp" + +namespace aidl { +namespace android { +namespace hardware { +namespace threadnetwork { + +Service::Service(char* urls[], int numUrls) : mBinderFd(-1) { + int fd; + + CHECK_NE(urls, nullptr); + CHECK_GT(numUrls, 0); + + for (int i = 0; i < numUrls; i++) { + auto threadChip = ndk::SharedRefBase::make(i, urls[i]); + CHECK_NE(threadChip, nullptr); + mThreadChips.push_back(std::move(threadChip)); + } + + binder_status_t status = ABinderProcess_setupPolling(&fd); + CHECK_EQ(status, ::STATUS_OK); + CHECK_GE(fd, 0); + mBinderFd.reset(fd); +} + +void Service::Update(otSysMainloopContext& context) { + FD_SET(mBinderFd.get(), &context.mReadFdSet); + context.mMaxFd = std::max(context.mMaxFd, mBinderFd.get()); +} + +void Service::Process(const otSysMainloopContext& context) { + if (FD_ISSET(mBinderFd.get(), &context.mReadFdSet)) { + ABinderProcess_handlePolledCommands(); + } +} + +void Service::startLoop(void) { + const struct timeval kPollTimeout = {1, 0}; + otSysMainloopContext context; + int rval; + + ot::Posix::Mainloop::Manager::Get().Add(*this); + + while (true) { + context.mMaxFd = -1; + context.mTimeout = kPollTimeout; + + FD_ZERO(&context.mReadFdSet); + FD_ZERO(&context.mWriteFdSet); + FD_ZERO(&context.mErrorFdSet); + + ot::Posix::Mainloop::Manager::Get().Update(context); + + rval = select(context.mMaxFd + 1, &context.mReadFdSet, &context.mWriteFdSet, + &context.mErrorFdSet, &context.mTimeout); + + if (rval >= 0) { + ot::Posix::Mainloop::Manager::Get().Process(context); + } else if (errno != EINTR) { + ALOGE("select() failed: %s", strerror(errno)); + break; + } + } +} +} // namespace threadnetwork +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/staging/threadnetwork/aidl/default/service.hpp b/staging/threadnetwork/aidl/default/service.hpp new file mode 100644 index 0000000000..6e6e86845a --- /dev/null +++ b/staging/threadnetwork/aidl/default/service.hpp @@ -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. + */ + +#include + +#include "mainloop.hpp" +#include "thread_chip.hpp" + +namespace aidl { +namespace android { +namespace hardware { +namespace threadnetwork { + +class Service : public ot::Posix::Mainloop::Source { + public: + Service(char* urls[], int numUrls); + + void Update(otSysMainloopContext& context) override; + void Process(const otSysMainloopContext& context) override; + void startLoop(void); + + private: + ::android::base::unique_fd mBinderFd; + std::vector> mThreadChips; +}; +} // namespace threadnetwork +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/staging/threadnetwork/aidl/default/thread_chip.cpp b/staging/threadnetwork/aidl/default/thread_chip.cpp new file mode 100644 index 0000000000..38abad4ab5 --- /dev/null +++ b/staging/threadnetwork/aidl/default/thread_chip.cpp @@ -0,0 +1,197 @@ +/* + * 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 "thread_chip.hpp" + +#include +#include +#include +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace threadnetwork { + +static ndk::ScopedAStatus errorStatus(int32_t error, const char* message) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(error, message)); +} + +ThreadChip::ThreadChip(uint8_t id, char* url) + : mUrl(), + mInterface(handleReceivedFrame, this, mRxFrameBuffer), + mRxFrameBuffer(), + mCallback(nullptr) { + const std::string name(std::string() + IThreadChip::descriptor + "/chip" + std::to_string(id)); + binder_status_t status; + + ALOGI("ServiceName: %s, Url: %s", name.c_str(), url); + CHECK_EQ(mUrl.Init(url), 0); + status = AServiceManager_addService(asBinder().get(), name.c_str()); + CHECK_EQ(status, STATUS_OK); + + mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient( + AIBinder_DeathRecipient_new(ThreadChip::onBinderDied)); + AIBinder_DeathRecipient_setOnUnlinked(mDeathRecipient.get(), ThreadChip::onBinderUnlinked); +} + +ThreadChip::~ThreadChip() { + AIBinder_DeathRecipient_delete(mDeathRecipient.get()); +} + +void ThreadChip::onBinderDied(void* context) { + reinterpret_cast(context)->onBinderDied(); +} + +void ThreadChip::onBinderDied(void) { + ALOGW("Thread Network HAL client is dead."); +} + +void ThreadChip::onBinderUnlinked(void* context) { + reinterpret_cast(context)->onBinderUnlinked(); +} + +void ThreadChip::onBinderUnlinked(void) { + ALOGW("ThreadChip binder is unlinked."); + deinitChip(); +} + +void ThreadChip::handleReceivedFrame(void* context) { + reinterpret_cast(context)->handleReceivedFrame(); +} + +void ThreadChip::handleReceivedFrame(void) { + if (mCallback != nullptr) { + mCallback->onReceiveSpinelFrame(std::vector( + mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetFrame() + mRxFrameBuffer.GetLength())); + } + + mRxFrameBuffer.DiscardFrame(); +} + +ndk::ScopedAStatus ThreadChip::open(const std::shared_ptr& in_callback) { + ndk::ScopedAStatus status = initChip(in_callback); + + if (status.isOk()) { + AIBinder_linkToDeath(in_callback->asBinder().get(), mDeathRecipient.get(), this); + ALOGI("Open IThreadChip successfully."); + } else { + ALOGW("Open IThreadChip failed, error: %s", status.getDescription().c_str()); + } + + return status; +} + +ndk::ScopedAStatus ThreadChip::initChip(const std::shared_ptr& in_callback) { + if (in_callback == nullptr) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } else if (mCallback == nullptr) { + if (mInterface.Init(mUrl) != OT_ERROR_NONE) { + return errorStatus(ERROR_FAILED, "Failed to initialize the interface"); + } + + mCallback = in_callback; + ot::Posix::Mainloop::Manager::Get().Add(*this); + return ndk::ScopedAStatus::ok(); + } else { + return errorStatus(ERROR_BUSY, "Interface is already opened"); + } +} + +ndk::ScopedAStatus ThreadChip::close() { + ndk::ScopedAStatus status; + std::shared_ptr callback = mCallback; + + status = deinitChip(); + if (status.isOk()) { + if (callback != nullptr) { + AIBinder_unlinkToDeath(callback->asBinder().get(), mDeathRecipient.get(), this); + } + + ALOGI("Close IThreadChip successfully"); + } else { + ALOGW("Close IThreadChip failed, error: %s", status.getDescription().c_str()); + } + + return status; +} + +ndk::ScopedAStatus ThreadChip::deinitChip() { + if (mCallback != nullptr) { + mInterface.Deinit(); + ot::Posix::Mainloop::Manager::Get().Remove(*this); + mCallback = nullptr; + return ndk::ScopedAStatus::ok(); + } + + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); +} + +ndk::ScopedAStatus ThreadChip::sendSpinelFrame(const std::vector& in_frame) { + ndk::ScopedAStatus status; + otError error; + + if (mCallback == nullptr) { + status = errorStatus(ERROR_FAILED, "The interface is not open"); + } else { + error = mInterface.SendFrame(reinterpret_cast(in_frame.data()), + in_frame.size()); + if (error == OT_ERROR_NONE) { + status = ndk::ScopedAStatus::ok(); + } else if (error == OT_ERROR_NO_BUFS) { + status = errorStatus(ERROR_NO_BUFS, "Insufficient buffer space to send"); + } else if (error == OT_ERROR_BUSY) { + status = errorStatus(ERROR_BUSY, "The interface is busy"); + } else { + status = errorStatus(ERROR_FAILED, "Failed to send the spinel frame"); + } + } + + if (!status.isOk()) { + ALOGW("Send spinel frame failed, error: %s", status.getDescription().c_str()); + } + + return status; +} + +ndk::ScopedAStatus ThreadChip::reset() { + mInterface.OnRcpReset(); + ALOGI("reset()"); + return ndk::ScopedAStatus::ok(); +} + +void ThreadChip::Update(otSysMainloopContext& context) { + if (mCallback != nullptr) { + mInterface.UpdateFdSet(context.mReadFdSet, context.mWriteFdSet, context.mMaxFd, + context.mTimeout); + } +} + +void ThreadChip::Process(const otSysMainloopContext& context) { + struct RadioProcessContext radioContext; + + if (mCallback != nullptr) { + radioContext.mReadFdSet = &context.mReadFdSet; + radioContext.mWriteFdSet = &context.mWriteFdSet; + mInterface.Process(radioContext); + } +} +} // namespace threadnetwork +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/staging/threadnetwork/aidl/default/thread_chip.hpp b/staging/threadnetwork/aidl/default/thread_chip.hpp new file mode 100644 index 0000000000..da5cba7585 --- /dev/null +++ b/staging/threadnetwork/aidl/default/thread_chip.hpp @@ -0,0 +1,68 @@ +/* + * 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 "hdlc_interface.hpp" +#include "lib/spinel/spinel_interface.hpp" +#include "mainloop.hpp" + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace threadnetwork { + +class ThreadChip : public BnThreadChip, ot::Posix::Mainloop::Source { + public: + ThreadChip(uint8_t id, char* url); + ~ThreadChip(); + + ndk::ScopedAStatus open(const std::shared_ptr& in_callback) override; + ndk::ScopedAStatus close() override; + ndk::ScopedAStatus sendSpinelFrame(const std::vector& in_frame) override; + ndk::ScopedAStatus reset() override; + void Update(otSysMainloopContext& context) override; + void Process(const otSysMainloopContext& context) override; + + private: + static void onBinderDied(void* context); + void onBinderDied(void); + static void onBinderUnlinked(void* context); + void onBinderUnlinked(void); + static void handleReceivedFrame(void* context); + void handleReceivedFrame(void); + + ndk::ScopedAStatus initChip(const std::shared_ptr& in_callback); + ndk::ScopedAStatus deinitChip(); + + ot::Url::Url mUrl; + ot::Posix::HdlcInterface mInterface; + ot::Spinel::SpinelInterface::RxFrameBuffer mRxFrameBuffer; + std::shared_ptr mCallback; + ::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient; +}; + +} // namespace threadnetwork +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/staging/threadnetwork/aidl/default/utils.cpp b/staging/threadnetwork/aidl/default/utils.cpp new file mode 100644 index 0000000000..d3b4062ee3 --- /dev/null +++ b/staging/threadnetwork/aidl/default/utils.cpp @@ -0,0 +1,34 @@ +/* + * 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 + +void otLogCritPlat(const char* format, ...) { + va_list args; + + va_start(args, format); + __android_log_vprint(ANDROID_LOG_FATAL, LOG_TAG, format, args); + va_end(args); +} + +void otLogWarnPlat(const char* format, ...) { + va_list args; + + va_start(args, format); + __android_log_vprint(ANDROID_LOG_WARN, LOG_TAG, format, args); + va_end(args); +} diff --git a/staging/threadnetwork/aidl/vts/Android.bp b/staging/threadnetwork/aidl/vts/Android.bp new file mode 100644 index 0000000000..70386d98be --- /dev/null +++ b/staging/threadnetwork/aidl/vts/Android.bp @@ -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. +// + +cc_test { + name: "VtsHalThreadNetworkTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsHalThreadNetworkTargetTest.cpp", + ], + + shared_libs: [ + "libbinder", + "libbinder_ndk", + ], + static_libs: [ + "android.hardware.threadnetwork-ndk", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/staging/threadnetwork/aidl/vts/VtsHalThreadNetworkTargetTest.cpp b/staging/threadnetwork/aidl/vts/VtsHalThreadNetworkTargetTest.cpp new file mode 100644 index 0000000000..3e43f9c8b7 --- /dev/null +++ b/staging/threadnetwork/aidl/vts/VtsHalThreadNetworkTargetTest.cpp @@ -0,0 +1,149 @@ +/* + * 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 "ThreadNetworkHalTargetTest" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using aidl::android::hardware::threadnetwork::BnThreadChipCallback; +using aidl::android::hardware::threadnetwork::IThreadChip; +using android::ProcessState; +using ndk::ScopedAStatus; +using ndk::SpAIBinder; + +namespace { +constexpr static int kCallbackTimeoutMs = 5000; +} // namespace + +class ThreadChipCallback : public BnThreadChipCallback { + public: + ThreadChipCallback(const std::function&)>& on_spinel_message_cb) + : on_spinel_message_cb_(on_spinel_message_cb) {} + + ScopedAStatus onReceiveSpinelFrame(const std::vector& in_aFrame) { + on_spinel_message_cb_(in_aFrame); + return ScopedAStatus::ok(); + } + + private: + std::function&)> on_spinel_message_cb_; +}; + +class ThreadNetworkAidl : public testing::TestWithParam { + public: + virtual void SetUp() override { + std::string serviceName = GetParam(); + + ALOGI("serviceName: %s", serviceName.c_str()); + + thread_chip = IThreadChip::fromBinder( + SpAIBinder(AServiceManager_waitForService(serviceName.c_str()))); + ASSERT_NE(thread_chip, nullptr); + } + + virtual void TearDown() override { thread_chip->close(); } + + std::shared_ptr thread_chip; +}; + +TEST_P(ThreadNetworkAidl, Open) { + std::shared_ptr callback = + ndk::SharedRefBase::make([](auto /* data */) {}); + + EXPECT_TRUE(thread_chip->open(callback).isOk()); + EXPECT_EQ(thread_chip->open(callback).getServiceSpecificError(), IThreadChip::ERROR_BUSY); +} + +TEST_P(ThreadNetworkAidl, Close) { + std::shared_ptr callback = + ndk::SharedRefBase::make([](auto /* data */) {}); + + EXPECT_TRUE(thread_chip->open(callback).isOk()); + EXPECT_TRUE(thread_chip->close().isOk()); + EXPECT_EQ(thread_chip->close().getExceptionCode(), EX_ILLEGAL_STATE); +} + +TEST_P(ThreadNetworkAidl, Reset) { + std::shared_ptr callback = + ndk::SharedRefBase::make([](auto /* data */) {}); + + EXPECT_TRUE(thread_chip->open(callback).isOk()); + EXPECT_TRUE(thread_chip->reset().isOk()); +} + +TEST_P(ThreadNetworkAidl, SendSpinelFrame) { + const uint8_t kCmdOffset = 2; + const uint8_t kMajorVersionOffset = 3; + const uint8_t kMinorVersionOffset = 4; + const std::vector kGetSpinelProtocolVersion({0x81, 0x02, 0x01}); + const std::vector kGetSpinelProtocolVersionResponse({0x81, 0x06, 0x01, 0x04, 0x03}); + uint8_t min_major_version = kGetSpinelProtocolVersionResponse[kMajorVersionOffset]; + uint8_t min_minor_version = kGetSpinelProtocolVersionResponse[kMinorVersionOffset]; + uint8_t major_version; + uint8_t minor_version; + std::promise open_cb_promise; + std::future open_cb_future{open_cb_promise.get_future()}; + std::shared_ptr callback; + std::vector received_frame; + std::chrono::milliseconds timeout{kCallbackTimeoutMs}; + + callback = ndk::SharedRefBase::make( + [&](const std::vector& in_aFrame) { + if (in_aFrame.size() == kGetSpinelProtocolVersionResponse.size() && + in_aFrame[kCmdOffset] == kGetSpinelProtocolVersionResponse[kCmdOffset]) { + major_version = in_aFrame[kMajorVersionOffset]; + minor_version = in_aFrame[kMinorVersionOffset]; + open_cb_promise.set_value(); + } + }); + + ASSERT_NE(callback, nullptr); + + EXPECT_TRUE(thread_chip->open(callback).isOk()); + + EXPECT_TRUE(thread_chip->sendSpinelFrame(kGetSpinelProtocolVersion).isOk()); + EXPECT_EQ(open_cb_future.wait_for(timeout), std::future_status::ready); + + EXPECT_GE(major_version, min_major_version); + if (major_version == min_major_version) { + EXPECT_GE(minor_version, min_minor_version); + } +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ThreadNetworkAidl); +INSTANTIATE_TEST_SUITE_P( + Thread, ThreadNetworkAidl, + testing::ValuesIn(android::getAidlHalInstanceNames(IThreadChip::descriptor)), + android::PrintInstanceNameToString); + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ProcessState::self()->setThreadPoolMaxThreadCount(1); + ProcessState::self()->startThreadPool(); + return RUN_ALL_TESTS(); +}