blob: e2745518eaa06d7dc225ae579cc221a77ef8b1f6 [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/common/discovery.h"
#include "chpp/macros.h"
#include "chpp/memory.h"
#include "chpp/platform/log.h"
#include "chpp/transport.h"
/************************************************
* Prototypes
***********************************************/
static inline bool chppIsClientCompatibleWithService(
const struct ChppClientDescriptor *client,
const struct ChppServiceDescriptor *service);
static uint8_t chppFindMatchingClient(
struct ChppAppState *context, const struct ChppServiceDescriptor *service);
static void chppDiscoveryProcessDiscoverAll(struct ChppAppState *context,
const uint8_t *buf, size_t len);
/************************************************
* 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);
}
/**
* Attempts to match a registered client to a (discovered) service, responding
* with either the client index or CHPP_CLIENT_INDEX_NONE if it fails.
*
* @param context Maintains status for each app layer instance.
* @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 chppFindMatchingClient(
struct ChppAppState *context, const struct ChppServiceDescriptor *service) {
uint8_t result = CHPP_CLIENT_INDEX_NONE;
for (size_t i = 0; i < context->registeredClientCount; i++) {
if (chppIsClientCompatibleWithService(
&context->registeredClients[i]->descriptor, service)) {
result = i;
break;
}
}
return result;
}
/**
* Processes the Discover All Services response
* (CHPP_DISCOVERY_COMMAND_DISCOVER_ALL).
*
* @param context Maintains status for each app layer instance.
* @param buf Input (request) datagram. Cannot be null.
* @param len Length of input data in bytes.
*/
static void chppDiscoveryProcessDiscoverAll(struct ChppAppState *context,
const uint8_t *buf, size_t len) {
CHPP_DEBUG_ASSERT(len >= sizeof(struct ChppAppHeader));
const struct ChppDiscoveryResponse *response =
(struct ChppDiscoveryResponse *)buf;
size_t servicesLen = len - sizeof(struct ChppAppHeader);
uint8_t serviceCount = servicesLen / sizeof(struct ChppServiceDescriptor);
if (servicesLen != serviceCount * sizeof(struct ChppServiceDescriptor)) {
// Incomplete service list
CHPP_LOGE("Service descriptors length length=%" PRIuSIZE
" is invalid for a service count = %" PRIu8
" and descriptor length = %" PRIuSIZE,
servicesLen, serviceCount, sizeof(struct ChppServiceDescriptor));
CHPP_DEBUG_ASSERT(false);
}
if (serviceCount >= CHPP_MAX_DISCOVERED_SERVICES) {
CHPP_LOGE("Discovered service count = %" PRIu8
" larger than CHPP_MAX_DISCOVERED_SERVICES = %d",
serviceCount, CHPP_MAX_DISCOVERED_SERVICES);
CHPP_DEBUG_ASSERT(false);
}
CHPP_LOGI("Attempting to match %" PRIu8 " registered clients and %" PRIu8
" discovered services",
context->registeredClientCount, serviceCount);
uint8_t matchedClients = 0;
for (uint8_t i = 0; i < MIN(serviceCount, CHPP_MAX_DISCOVERED_SERVICES);
i++) {
// Update lookup table
context->clientIndexOfServiceIndex[i] =
chppFindMatchingClient(context, &response->services[i]);
char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
chppUuidToStr(response->services[i].uuid, uuidText);
if (context->clientIndexOfServiceIndex[i] == CHPP_CLIENT_INDEX_NONE) {
CHPP_LOGI(
"No matching client found for service on handle %d"
" with name=%s, UUID=%s, version=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
CHPP_SERVICE_HANDLE_OF_INDEX(i), response->services[i].name, uuidText,
response->services[i].version.major,
response->services[i].version.minor,
response->services[i].version.patch);
} else {
CHPP_LOGI(
"Client # %" PRIu8
" matched to service on handle %d"
" with name=%s, UUID=%s. "
"client version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
", service version=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
context->clientIndexOfServiceIndex[i],
CHPP_SERVICE_HANDLE_OF_INDEX(i), response->services[i].name, uuidText,
context->registeredClients[context->clientIndexOfServiceIndex[i]]
->descriptor.version.major,
context->registeredClients[context->clientIndexOfServiceIndex[i]]
->descriptor.version.minor,
context->registeredClients[context->clientIndexOfServiceIndex[i]]
->descriptor.version.patch,
response->services[i].version.major,
response->services[i].version.minor,
response->services[i].version.patch);
// Initialize client
uint8_t idx = context->clientIndexOfServiceIndex[i];
if (context->registeredClients[idx]->initFunctionPtr(
context->registeredClientContexts[idx],
CHPP_SERVICE_HANDLE_OF_INDEX(i),
response->services[i].version) == false) {
CHPP_LOGE(
"Client rejected initialization (maybe due to incompatible "
"versions?) client version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
", service version=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
context->registeredClients[context->clientIndexOfServiceIndex[i]]
->descriptor.version.major,
context->registeredClients[context->clientIndexOfServiceIndex[i]]
->descriptor.version.minor,
context->registeredClients[context->clientIndexOfServiceIndex[i]]
->descriptor.version.patch,
response->services[i].version.major,
response->services[i].version.minor,
response->services[i].version.patch);
} else {
matchedClients++;
}
}
}
CHPP_LOGI("Successfully matched %" PRIu8
" clients with services, out of a total of %" PRIu8
" registered clients and %" PRIu8 " discovered services",
matchedClients, context->registeredClientCount, serviceCount);
}
/************************************************
* Public Functions
***********************************************/
bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *context,
const uint8_t *buf, size_t len) {
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
bool success = true;
switch (rxHeader->command) {
case CHPP_DISCOVERY_COMMAND_DISCOVER_ALL: {
chppDiscoveryProcessDiscoverAll(context, buf, len);
break;
}
default: {
success = false;
break;
}
}
return success;
}
void chppInitiateDiscovery(struct ChppAppState *context) {
for (uint8_t i = 0; i < CHPP_MAX_DISCOVERED_SERVICES; i++) {
context->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->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL;
chppEnqueueTxDatagramOrFail(context->transportContext, request,
sizeof(struct ChppAppHeader));
}