Merge "Add Thread network HAL"

This commit is contained in:
Zhanglong Xia
2023-02-14 22:43:59 +00:00
committed by Android (Google) Code Review
14 changed files with 841 additions and 0 deletions

View File

@@ -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

View File

@@ -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,
},
},
}

View File

@@ -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()` wont be reset and the resource allocated by `open()` wont 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);
}

View File

@@ -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);
}

View File

@@ -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"],
}

View File

@@ -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

View File

@@ -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 <android-base/logging.h>
#include <utils/Log.h>
#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
}

View File

@@ -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 <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <utils/Log.h>
#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<ThreadChip>(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

View File

@@ -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 <android-base/unique_fd.h>
#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<std::shared_ptr<ThreadChip>> mThreadChips;
};
} // namespace threadnetwork
} // namespace hardware
} // namespace android
} // namespace aidl

View File

@@ -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 <android-base/logging.h>
#include <android/binder_auto_utils.h>
#include <android/binder_ibinder.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <utils/Log.h>
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<ThreadChip*>(context)->onBinderDied();
}
void ThreadChip::onBinderDied(void) {
ALOGW("Thread Network HAL client is dead.");
}
void ThreadChip::onBinderUnlinked(void* context) {
reinterpret_cast<ThreadChip*>(context)->onBinderUnlinked();
}
void ThreadChip::onBinderUnlinked(void) {
ALOGW("ThreadChip binder is unlinked.");
deinitChip();
}
void ThreadChip::handleReceivedFrame(void* context) {
reinterpret_cast<ThreadChip*>(context)->handleReceivedFrame();
}
void ThreadChip::handleReceivedFrame(void) {
if (mCallback != nullptr) {
mCallback->onReceiveSpinelFrame(std::vector<uint8_t>(
mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetFrame() + mRxFrameBuffer.GetLength()));
}
mRxFrameBuffer.DiscardFrame();
}
ndk::ScopedAStatus ThreadChip::open(const std::shared_ptr<IThreadChipCallback>& 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<IThreadChipCallback>& 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<IThreadChipCallback> 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<uint8_t>& 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<const uint8_t*>(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

View File

@@ -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 <aidl/android/hardware/threadnetwork/BnThreadChip.h>
#include <aidl/android/hardware/threadnetwork/IThreadChipCallback.h>
#include "hdlc_interface.hpp"
#include "lib/spinel/spinel_interface.hpp"
#include "mainloop.hpp"
#include <android/binder_auto_utils.h>
#include <android/binder_ibinder.h>
#include <utils/Mutex.h>
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<IThreadChipCallback>& in_callback) override;
ndk::ScopedAStatus close() override;
ndk::ScopedAStatus sendSpinelFrame(const std::vector<uint8_t>& 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<IThreadChipCallback>& in_callback);
ndk::ScopedAStatus deinitChip();
ot::Url::Url mUrl;
ot::Posix::HdlcInterface mInterface;
ot::Spinel::SpinelInterface::RxFrameBuffer mRxFrameBuffer;
std::shared_ptr<IThreadChipCallback> mCallback;
::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
};
} // namespace threadnetwork
} // namespace hardware
} // namespace android
} // namespace aidl

View File

@@ -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 <openthread/logging.h>
#include <utils/Log.h>
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);
}

View File

@@ -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",
],
}

View File

@@ -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 <future>
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <android-base/logging.h>
#include <android/binder_auto_utils.h>
#include <android/binder_manager.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <log/log.h>
#include <aidl/android/hardware/threadnetwork/BnThreadChipCallback.h>
#include <aidl/android/hardware/threadnetwork/IThreadChip.h>
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<void(const std::vector<uint8_t>&)>& on_spinel_message_cb)
: on_spinel_message_cb_(on_spinel_message_cb) {}
ScopedAStatus onReceiveSpinelFrame(const std::vector<uint8_t>& in_aFrame) {
on_spinel_message_cb_(in_aFrame);
return ScopedAStatus::ok();
}
private:
std::function<void(const std::vector<uint8_t>&)> on_spinel_message_cb_;
};
class ThreadNetworkAidl : public testing::TestWithParam<std::string> {
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<IThreadChip> thread_chip;
};
TEST_P(ThreadNetworkAidl, Open) {
std::shared_ptr<ThreadChipCallback> callback =
ndk::SharedRefBase::make<ThreadChipCallback>([](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<ThreadChipCallback> callback =
ndk::SharedRefBase::make<ThreadChipCallback>([](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<ThreadChipCallback> callback =
ndk::SharedRefBase::make<ThreadChipCallback>([](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<uint8_t> kGetSpinelProtocolVersion({0x81, 0x02, 0x01});
const std::vector<uint8_t> 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<void> open_cb_promise;
std::future<void> open_cb_future{open_cb_promise.get_future()};
std::shared_ptr<ThreadChipCallback> callback;
std::vector<uint8_t> received_frame;
std::chrono::milliseconds timeout{kCallbackTimeoutMs};
callback = ndk::SharedRefBase::make<ThreadChipCallback>(
[&](const std::vector<uint8_t>& 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();
}