/* * Copyright (C) 2023 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 #include #include "chpp/app.h" #include "chpp/clients.h" #include "chpp/clients/discovery.h" #include "chpp/macros.h" #include "chpp/notifier.h" #include "chpp/platform/platform_link.h" #include "chpp/platform/utils.h" #include "chpp/services.h" #include "chpp/transport.h" #include "chre/util/enum.h" #include "chre/util/time.h" namespace chre { namespace { constexpr uint64_t kResetWaitTimeMs = 5000; constexpr uint64_t kDiscoveryWaitTimeMs = 5000; void *workThread(void *transportState) { ChppTransportState *state = static_cast(transportState); auto linkContext = static_cast(state->linkContext); pthread_setname_np(pthread_self(), linkContext->workThreadName); chppWorkThreadStart(state); return nullptr; } #define TEST_UUID \ { \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x12 \ } enum class Commands : uint16_t { kServiceNotification, kClientNotification, }; constexpr uint16_t kNumCommands = 1; struct ClientState { struct ChppEndpointState chppClientState; struct ChppOutgoingRequestState outReqStates[kNumCommands]; bool serviceNotificationStatus; struct ChppNotifier notifier; }; bool clientInit(void *clientState, uint8_t handle, struct ChppVersion serviceVersion); void clientDeinit(void *clientState); enum ChppAppErrorCode clientDispatchNotification(void *clientState, uint8_t *buf, size_t len); constexpr struct ChppClient kClient = { .descriptor.uuid = TEST_UUID, .descriptor.version.major = 1, .descriptor.version.minor = 0, .descriptor.version.patch = 0, .resetNotifierFunctionPtr = nullptr, .matchNotifierFunctionPtr = nullptr, .responseDispatchFunctionPtr = nullptr, .notificationDispatchFunctionPtr = &clientDispatchNotification, .initFunctionPtr = &clientInit, .deinitFunctionPtr = &clientDeinit, .outReqCount = kNumCommands, .minLength = sizeof(struct ChppAppHeader), }; // Called when a notification from a service is received. enum ChppAppErrorCode clientDispatchNotification(void *clientState, uint8_t *buf, size_t len) { auto state = static_cast(clientState); // The response is composed of the app header only. if (len != sizeof(ChppAppHeader)) { return CHPP_APP_ERROR_NONE; } auto notification = reinterpret_cast(buf); switch (notification->command) { case asBaseType(Commands::kServiceNotification): state->serviceNotificationStatus = notification->error == CHPP_APP_ERROR_NONE; chppNotifierSignal(&state->notifier, 1 /*signal*/); return CHPP_APP_ERROR_NONE; default: return CHPP_APP_ERROR_NONE; } } bool clientInit(void *clientState, uint8_t handle, struct ChppVersion serviceVersion) { UNUSED_VAR(serviceVersion); auto state = static_cast(clientState); state->chppClientState.openState = CHPP_OPEN_STATE_OPENED; chppClientInit(&state->chppClientState, handle); return true; } void clientDeinit(void *clientState) { auto state = static_cast(clientState); chppClientDeinit(&state->chppClientState); state->chppClientState.openState = CHPP_OPEN_STATE_CLOSED; } // Service struct ServiceState { struct ChppEndpointState chppServiceState; struct ChppIncomingRequestState inReqStates[kNumCommands]; bool clientNotificationStatus; struct ChppNotifier notifier; }; // Called when a notification from a client is received. enum ChppAppErrorCode serviceDispatchNotification(void *serviceState, uint8_t *buf, size_t len) { auto state = static_cast(serviceState); // The response is composed of the app header only. if (len != sizeof(ChppAppHeader)) { return CHPP_APP_ERROR_NONE; } auto notification = reinterpret_cast(buf); switch (notification->command) { case asBaseType(Commands::kClientNotification): state->clientNotificationStatus = notification->error == CHPP_APP_ERROR_NONE; chppNotifierSignal(&state->notifier, 1 /*signal*/); return CHPP_APP_ERROR_NONE; default: return CHPP_APP_ERROR_NONE; } } const struct ChppService kService = { .descriptor.uuid = TEST_UUID, .descriptor.name = "Test", .descriptor.version.major = 1, .descriptor.version.minor = 0, .descriptor.version.patch = 0, .resetNotifierFunctionPtr = nullptr, .requestDispatchFunctionPtr = nullptr, .notificationDispatchFunctionPtr = &serviceDispatchNotification, .minLength = sizeof(struct ChppAppHeader), }; // Test notifications. class AppNotificationTest : public testing::Test { protected: void SetUp() { chppClearTotalAllocBytes(); chppNotifierInit(&mClientState.notifier); chppNotifierInit(&mServiceState.notifier); memset(&mClientLinkContext, 0, sizeof(mClientLinkContext)); memset(&mServiceLinkContext, 0, sizeof(mServiceLinkContext)); mServiceLinkContext.linkThreadName = "Host Link"; mServiceLinkContext.workThreadName = "Host worker"; mServiceLinkContext.isLinkActive = true; mServiceLinkContext.remoteLinkState = &mClientLinkContext; mServiceLinkContext.rxInRemoteEndpointWorker = false; mClientLinkContext.linkThreadName = "CHRE Link"; mClientLinkContext.workThreadName = "CHRE worker"; mClientLinkContext.isLinkActive = true; mClientLinkContext.remoteLinkState = &mServiceLinkContext; mClientLinkContext.rxInRemoteEndpointWorker = false; // No default clients/services. struct ChppClientServiceSet set; memset(&set, 0, sizeof(set)); const struct ChppLinkApi *linkApi = getLinuxLinkApi(); // Init client side. chppTransportInit(&mClientTransportContext, &mClientAppContext, &mClientLinkContext, linkApi); chppAppInitWithClientServiceSet(&mClientAppContext, &mClientTransportContext, set); // Init service side. chppTransportInit(&mServiceTransportContext, &mServiceAppContext, &mServiceLinkContext, linkApi); chppAppInitWithClientServiceSet(&mServiceAppContext, &mServiceTransportContext, set); BringUpClient(); std::this_thread::sleep_for(std::chrono::milliseconds(450)); BringUpService(); mClientLinkContext.linkEstablished = true; mServiceLinkContext.linkEstablished = true; EXPECT_TRUE(chppTransportWaitForResetComplete(&mClientTransportContext, kResetWaitTimeMs)); EXPECT_TRUE(chppTransportWaitForResetComplete(&mServiceTransportContext, kResetWaitTimeMs)); EXPECT_TRUE( chppWaitForDiscoveryComplete(&mClientAppContext, kDiscoveryWaitTimeMs)); EXPECT_TRUE(chppWaitForDiscoveryComplete(&mServiceAppContext, kDiscoveryWaitTimeMs)); } void BringUpClient() { memset(&mClientState, 0, sizeof(mClientState)); chppRegisterClient(&mClientAppContext, &mClientState, &mClientState.chppClientState, &mClientState.outReqStates[0], &kClient); pthread_create(&mClientWorkThread, NULL, workThread, &mClientTransportContext); } void BringUpService() { memset(&mServiceState, 0, sizeof(mServiceState)); chppRegisterService(&mServiceAppContext, &mServiceState, &mServiceState.chppServiceState, NULL /*outReqStates*/, &kService); pthread_create(&mServiceWorkThread, NULL, workThread, &mServiceTransportContext); } void TearDown() { chppNotifierDeinit(&mClientState.notifier); chppNotifierDeinit(&mServiceState.notifier); chppWorkThreadStop(&mClientTransportContext); chppWorkThreadStop(&mServiceTransportContext); pthread_join(mClientWorkThread, NULL); pthread_join(mServiceWorkThread, NULL); // Deinit client side. chppAppDeinit(&mClientAppContext); chppTransportDeinit(&mClientTransportContext); // Deinit service side. chppAppDeinit(&mServiceAppContext); chppTransportDeinit(&mServiceTransportContext); EXPECT_EQ(chppGetTotalAllocBytes(), 0); } // Client side. ChppLinuxLinkState mClientLinkContext = {}; ChppTransportState mClientTransportContext = {}; ChppAppState mClientAppContext = {}; pthread_t mClientWorkThread; ClientState mClientState; // Service side ChppLinuxLinkState mServiceLinkContext = {}; ChppTransportState mServiceTransportContext = {}; ChppAppState mServiceAppContext = {}; pthread_t mServiceWorkThread; ServiceState mServiceState; }; TEST_F(AppNotificationTest, serviceSendANotificationToClient) { // Send a notification. constexpr size_t notificationLen = sizeof(struct ChppAppHeader); struct ChppAppHeader *notification = chppAllocServiceNotification(notificationLen); ASSERT_NE(notification, nullptr); notification->command = asBaseType(Commands::kServiceNotification); notification->handle = mServiceState.chppServiceState.handle; mClientState.serviceNotificationStatus = false; EXPECT_TRUE(chppEnqueueTxDatagramOrFail(&mServiceTransportContext, notification, notificationLen)); chppNotifierWait(&mClientState.notifier); EXPECT_TRUE(mClientState.serviceNotificationStatus); } TEST_F(AppNotificationTest, clientSendANotificationToService) { // Send a notification. constexpr size_t notificationLen = sizeof(struct ChppAppHeader); struct ChppAppHeader *notification = chppAllocClientNotification(notificationLen); ASSERT_NE(notification, nullptr); notification->command = asBaseType(Commands::kClientNotification); notification->handle = mClientState.chppClientState.handle; mServiceState.clientNotificationStatus = false; EXPECT_TRUE(chppEnqueueTxDatagramOrFail(&mClientTransportContext, notification, notificationLen)); chppNotifierWait(&mServiceState.notifier); EXPECT_TRUE(mServiceState.clientNotificationStatus); } } // namespace } // namespace chre