blob: af314e2b23ffe75d3b7076afcff90f833f442582 [file] [log] [blame]
/*
* Copyright (C) 2020 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 "chpp/clients/discovery.h"
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "chpp/app.h"
#include "chpp/clients.h"
#include "chpp/common/discovery.h"
#include "chpp/log.h"
#include "chpp/macros.h"
#include "chpp/memory.h"
#include "chpp/transport.h"
/************************************************
* Prototypes
***********************************************/
static inline bool chppIsClientCompatibleWithService(
const struct ChppClientDescriptor *client,
const struct ChppServiceDescriptor *service);
static uint8_t chppFindMatchingClientIndex(
struct ChppAppState *appState, const struct ChppServiceDescriptor *service);
static void chppProcessDiscoverAllResponse(
struct ChppAppState *appState, const struct ChppDiscoveryResponse *response,
size_t responseLen);
static ChppNotifierFunction *chppGetClientMatchNotifierFunction(
struct ChppAppState *appState, uint8_t index);
/************************************************
* Private Functions
***********************************************/
/**
* Determines if a client is compatible with a service.
*
* Compatibility requirements are:
* 1. UUIDs must match
* 2. Major version numbers must match
*
* @param client ChppClientDescriptor of client.
* @param service ChppServiceDescriptor of service.
*
* @param return True if compatible.
*/
static inline bool chppIsClientCompatibleWithService(
const struct ChppClientDescriptor *client,
const struct ChppServiceDescriptor *service) {
return memcmp(client->uuid, service->uuid, CHPP_SERVICE_UUID_LEN) == 0 &&
client->version.major == service->version.major;
}
/**
* Matches a registered client to a (discovered) service.
*
* @param appState Application layer state.
* @param service ChppServiceDescriptor of service.
*
* @param return Index of client matching the service, or CHPP_CLIENT_INDEX_NONE
* if there is none.
*/
static uint8_t chppFindMatchingClientIndex(
struct ChppAppState *appState,
const struct ChppServiceDescriptor *service) {
uint8_t result = CHPP_CLIENT_INDEX_NONE;
const struct ChppClient **clients = appState->registeredClients;
for (uint8_t i = 0; i < appState->registeredClientCount; i++) {
if (chppIsClientCompatibleWithService(&clients[i]->descriptor, service)) {
result = i;
break;
}
}
return result;
}
/**
* Processes the Discover All Services response
* (CHPP_DISCOVERY_COMMAND_DISCOVER_ALL).
*
* @param appState Application layer state.
* @param response The response from the discovery service.
* @param responseLen Length of the in bytes.
*/
static void chppProcessDiscoverAllResponse(
struct ChppAppState *appState, const struct ChppDiscoveryResponse *response,
size_t responseLen) {
if (appState->isDiscoveryComplete) {
CHPP_LOGE("Dupe discovery resp");
return;
}
size_t servicesLen = responseLen - sizeof(struct ChppAppHeader);
uint8_t serviceCount =
(uint8_t)(servicesLen / sizeof(struct ChppServiceDescriptor));
CHPP_DEBUG_ASSERT_LOG(
servicesLen == serviceCount * sizeof(struct ChppServiceDescriptor),
"Discovery desc len=%" PRIuSIZE " != count=%" PRIu8 " * size=%" PRIuSIZE,
servicesLen, serviceCount, sizeof(struct ChppServiceDescriptor));
CHPP_DEBUG_ASSERT_LOG(serviceCount <= CHPP_MAX_DISCOVERED_SERVICES,
"Service count=%" PRIu8 " > max=%d", serviceCount,
CHPP_MAX_DISCOVERED_SERVICES);
CHPP_LOGI("Discovered %" PRIu8 " services", serviceCount);
uint8_t matchedClients = 0;
for (uint8_t i = 0; i < MIN(serviceCount, CHPP_MAX_DISCOVERED_SERVICES);
i++) {
const struct ChppServiceDescriptor *service = &response->services[i];
// Update lookup table
uint8_t clientIndex = chppFindMatchingClientIndex(appState, service);
appState->clientIndexOfServiceIndex[i] = clientIndex;
char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
chppUuidToStr(service->uuid, uuidText);
if (clientIndex == CHPP_CLIENT_INDEX_NONE) {
CHPP_LOGE(
"No client for service #%d"
" name=%s, UUID=%s, v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
CHPP_SERVICE_HANDLE_OF_INDEX(i), service->name, uuidText,
service->version.major, service->version.minor,
service->version.patch);
continue;
}
const struct ChppClient *client = appState->registeredClients[clientIndex];
CHPP_LOGD("Client # %" PRIu8
" matched to service on handle %d"
" with name=%s, UUID=%s. "
"client version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
", service version=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i), service->name,
uuidText, client->descriptor.version.major,
client->descriptor.version.minor,
client->descriptor.version.patch, service->version.major,
service->version.minor, service->version.patch);
// Initialize client
if (!client->initFunctionPtr(
appState->registeredClientStates[clientIndex]->context,
CHPP_SERVICE_HANDLE_OF_INDEX(i), service->version)) {
CHPP_LOGE("Client v=%" PRIu8 ".%" PRIu8 ".%" PRIu16
" rejected init. Service v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
client->descriptor.version.major,
client->descriptor.version.minor,
client->descriptor.version.patch, service->version.major,
service->version.minor, service->version.patch);
continue;
}
matchedClients++;
}
CHPP_LOGD("Matched %" PRIu8 " out of %" PRIu8 " clients and %" PRIu8
" services",
matchedClients, appState->registeredClientCount, serviceCount);
// Notify any clients waiting on discovery completion
chppMutexLock(&appState->discoveryMutex);
appState->isDiscoveryComplete = true;
appState->matchedClientCount = matchedClients;
appState->discoveredServiceCount = serviceCount;
chppConditionVariableSignal(&appState->discoveryCv);
chppMutexUnlock(&appState->discoveryMutex);
// Notify clients of match
for (uint8_t i = 0; i < appState->discoveredServiceCount; i++) {
uint8_t clientIndex = appState->clientIndexOfServiceIndex[i];
if (clientIndex != CHPP_CLIENT_INDEX_NONE) {
// Discovered service has a matched client
ChppNotifierFunction *matchNotifierFunction =
chppGetClientMatchNotifierFunction(appState, clientIndex);
CHPP_LOGD("Client #%" PRIu8 " (H#%d) match notifier found=%d",
clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i),
(matchNotifierFunction != NULL));
if (matchNotifierFunction != NULL) {
matchNotifierFunction(
appState->registeredClientStates[clientIndex]->context);
}
}
}
}
/**
* Returns the match notification function pointer of a particular negotiated
* client. The function pointer will be set to null by clients that do not need
* or support a match notification.
*
* @param appState Application layer state.
* @param index Index of the registered client.
*
* @return Pointer to the match notification function.
*/
static ChppNotifierFunction *chppGetClientMatchNotifierFunction(
struct ChppAppState *appState, uint8_t index) {
return appState->registeredClients[index]->matchNotifierFunctionPtr;
}
/************************************************
* Public Functions
***********************************************/
void chppDiscoveryInit(struct ChppAppState *appState) {
CHPP_ASSERT_LOG(!appState->isDiscoveryClientInitialized,
"Discovery client already initialized");
CHPP_LOGD("Initializing CHPP discovery client");
if (!appState->isDiscoveryClientInitialized) {
chppMutexInit(&appState->discoveryMutex);
chppConditionVariableInit(&appState->discoveryCv);
appState->isDiscoveryClientInitialized = true;
}
appState->matchedClientCount = 0;
appState->isDiscoveryComplete = false;
appState->isDiscoveryClientInitialized = true;
}
void chppDiscoveryDeinit(struct ChppAppState *appState) {
CHPP_ASSERT_LOG(appState->isDiscoveryClientInitialized,
"Discovery client already deinitialized");
CHPP_LOGD("Deinitializing CHPP discovery client");
appState->isDiscoveryClientInitialized = false;
}
bool chppWaitForDiscoveryComplete(struct ChppAppState *appState,
uint64_t timeoutMs) {
bool success = false;
if (!appState->isDiscoveryClientInitialized) {
timeoutMs = 0;
} else {
success = true;
chppMutexLock(&appState->discoveryMutex);
if (timeoutMs == 0) {
success = appState->isDiscoveryComplete;
} else {
while (success && !appState->isDiscoveryComplete) {
success = chppConditionVariableTimedWait(
&appState->discoveryCv, &appState->discoveryMutex,
timeoutMs * CHPP_NSEC_PER_MSEC);
}
}
chppMutexUnlock(&appState->discoveryMutex);
}
if (!success) {
CHPP_LOGE("Discovery incomplete after %" PRIu64 " ms", timeoutMs);
}
return success;
}
bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *appState,
const uint8_t *buf, size_t len) {
const struct ChppAppHeader *rxHeader = (const struct ChppAppHeader *)buf;
bool success = true;
switch (rxHeader->command) {
case CHPP_DISCOVERY_COMMAND_DISCOVER_ALL: {
chppProcessDiscoverAllResponse(
appState, (const struct ChppDiscoveryResponse *)buf, len);
break;
}
default: {
success = false;
break;
}
}
return success;
}
void chppInitiateDiscovery(struct ChppAppState *appState) {
if (appState->isDiscoveryComplete) {
CHPP_LOGE("Duplicate discovery init");
return;
}
for (uint8_t i = 0; i < CHPP_MAX_DISCOVERED_SERVICES; i++) {
appState->clientIndexOfServiceIndex[i] = CHPP_CLIENT_INDEX_NONE;
}
struct ChppAppHeader *request = chppMalloc(sizeof(struct ChppAppHeader));
request->handle = CHPP_HANDLE_DISCOVERY;
request->type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
request->transaction = 0;
request->error = CHPP_APP_ERROR_NONE;
request->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL;
chppEnqueueTxDatagramOrFail(appState->transportContext, request,
sizeof(*request));
}
bool chppAreAllClientsMatched(struct ChppAppState *appState) {
bool success = false;
chppMutexLock(&appState->discoveryMutex);
success = (appState->isDiscoveryComplete) &&
(appState->registeredClientCount == appState->matchedClientCount);
chppMutexUnlock(&appState->discoveryMutex);
return success;
}