1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "chpp/clients/discovery.h" 18 19 #include <inttypes.h> 20 #include <stddef.h> 21 #include <stdint.h> 22 #include <string.h> 23 24 #include "chpp/app.h" 25 #include "chpp/common/discovery.h" 26 #include "chpp/log.h" 27 #include "chpp/macros.h" 28 #include "chpp/memory.h" 29 #include "chpp/transport.h" 30 31 /************************************************ 32 * Prototypes 33 ***********************************************/ 34 35 static inline bool chppIsClientCompatibleWithService( 36 const struct ChppClientDescriptor *client, 37 const struct ChppServiceDescriptor *service); 38 static uint8_t chppFindMatchingClient( 39 struct ChppAppState *context, const struct ChppServiceDescriptor *service); 40 static void chppDiscoveryProcessDiscoverAll(struct ChppAppState *context, 41 const uint8_t *buf, size_t len); 42 ChppNotifierFunction *chppGetClientMatchNotifierFunction( 43 struct ChppAppState *context, uint8_t index); 44 45 /************************************************ 46 * Private Functions 47 ***********************************************/ 48 49 /** 50 * Determines if a client is compatible with a service. Compatibility 51 * requirements are: 52 * 1. UUIDs must match 53 * 2. Major version numbers must match 54 * 55 * @param client ChppClientDescriptor of client. 56 * @param service ChppServiceDescriptor of service. 57 * 58 * @param return True if compatible. 59 */ 60 static inline bool chppIsClientCompatibleWithService( 61 const struct ChppClientDescriptor *client, 62 const struct ChppServiceDescriptor *service) { 63 return (memcmp(client->uuid, service->uuid, CHPP_SERVICE_UUID_LEN) == 0 && 64 client->version.major == service->version.major); 65 } 66 67 /** 68 * Attempts to match a registered client to a (discovered) service, responding 69 * with either the client index or CHPP_CLIENT_INDEX_NONE if it fails. 70 * 71 * @param context Maintains status for each app layer instance. 72 * @param service ChppServiceDescriptor of service. 73 * 74 * @param return Index of client matching the service, or CHPP_CLIENT_INDEX_NONE 75 * if there is none. 76 */ 77 static uint8_t chppFindMatchingClient( 78 struct ChppAppState *context, const struct ChppServiceDescriptor *service) { 79 uint8_t result = CHPP_CLIENT_INDEX_NONE; 80 81 for (uint8_t i = 0; i < context->registeredClientCount; i++) { 82 if (chppIsClientCompatibleWithService( 83 &context->registeredClients[i]->descriptor, service)) { 84 result = i; 85 break; 86 } 87 } 88 89 return result; 90 } 91 92 /** 93 * Processes the Discover All Services response 94 * (CHPP_DISCOVERY_COMMAND_DISCOVER_ALL). 95 * 96 * @param context Maintains status for each app layer instance. 97 * @param buf Input (request) datagram. Cannot be null. 98 * @param len Length of input data in bytes. 99 */ 100 static void chppDiscoveryProcessDiscoverAll(struct ChppAppState *context, 101 const uint8_t *buf, size_t len) { 102 if (context->isDiscoveryComplete) { 103 CHPP_LOGE("Duplicate discovery response"); 104 return; 105 } 106 107 const struct ChppDiscoveryResponse *response = 108 (const struct ChppDiscoveryResponse *)buf; 109 size_t servicesLen = len - sizeof(struct ChppAppHeader); 110 uint8_t serviceCount = 111 (uint8_t)(servicesLen / sizeof(struct ChppServiceDescriptor)); 112 113 if (servicesLen != serviceCount * sizeof(struct ChppServiceDescriptor)) { 114 // Incomplete service list 115 CHPP_LOGE("Descriptor len=%" PRIuSIZE " doesn't match count=%" PRIu8 116 " and size=%" PRIuSIZE, 117 servicesLen, serviceCount, sizeof(struct ChppServiceDescriptor)); 118 CHPP_DEBUG_ASSERT(false); 119 } 120 121 if (serviceCount > CHPP_MAX_DISCOVERED_SERVICES) { 122 CHPP_LOGE("Service count=%" PRIu8 " larger than max=%d", serviceCount, 123 CHPP_MAX_DISCOVERED_SERVICES); 124 CHPP_DEBUG_ASSERT(false); 125 } 126 127 CHPP_LOGI("Discovered %" PRIu8 " services", serviceCount); 128 129 uint8_t matchedClients = 0; 130 for (uint8_t i = 0; i < MIN(serviceCount, CHPP_MAX_DISCOVERED_SERVICES); 131 i++) { 132 // Update lookup table 133 context->clientIndexOfServiceIndex[i] = 134 chppFindMatchingClient(context, &response->services[i]); 135 136 char uuidText[CHPP_SERVICE_UUID_STRING_LEN]; 137 chppUuidToStr(response->services[i].uuid, uuidText); 138 139 if (context->clientIndexOfServiceIndex[i] == CHPP_CLIENT_INDEX_NONE) { 140 CHPP_LOGE( 141 "No matching client for service: %d" 142 " name=%s, UUID=%s, version=%" PRIu8 ".%" PRIu8 ".%" PRIu16, 143 CHPP_SERVICE_HANDLE_OF_INDEX(i), response->services[i].name, uuidText, 144 response->services[i].version.major, 145 response->services[i].version.minor, 146 response->services[i].version.patch); 147 148 } else { 149 CHPP_LOGD( 150 "Client # %" PRIu8 151 " matched to service on handle %d" 152 " with name=%s, UUID=%s. " 153 "client version=%" PRIu8 ".%" PRIu8 ".%" PRIu16 154 ", service version=%" PRIu8 ".%" PRIu8 ".%" PRIu16, 155 context->clientIndexOfServiceIndex[i], 156 CHPP_SERVICE_HANDLE_OF_INDEX(i), response->services[i].name, uuidText, 157 context->registeredClients[context->clientIndexOfServiceIndex[i]] 158 ->descriptor.version.major, 159 context->registeredClients[context->clientIndexOfServiceIndex[i]] 160 ->descriptor.version.minor, 161 context->registeredClients[context->clientIndexOfServiceIndex[i]] 162 ->descriptor.version.patch, 163 response->services[i].version.major, 164 response->services[i].version.minor, 165 response->services[i].version.patch); 166 167 // Initialize client 168 uint8_t idx = context->clientIndexOfServiceIndex[i]; 169 if (context->registeredClients[idx]->initFunctionPtr( 170 context->registeredClientContexts[idx], 171 CHPP_SERVICE_HANDLE_OF_INDEX(i), 172 response->services[i].version) == false) { 173 CHPP_LOGE( 174 "Client rejected init: client ver=%" PRIu8 ".%" PRIu8 ".%" PRIu16 175 ", service ver=%" PRIu8 ".%" PRIu8 ".%" PRIu16, 176 context->registeredClients[context->clientIndexOfServiceIndex[i]] 177 ->descriptor.version.major, 178 context->registeredClients[context->clientIndexOfServiceIndex[i]] 179 ->descriptor.version.minor, 180 context->registeredClients[context->clientIndexOfServiceIndex[i]] 181 ->descriptor.version.patch, 182 response->services[i].version.major, 183 response->services[i].version.minor, 184 response->services[i].version.patch); 185 } else { 186 matchedClients++; 187 } 188 } 189 } 190 191 CHPP_LOGD("Matched %" PRIu8 " out of %" PRIu8 " clients and %" PRIu8 192 " services", 193 matchedClients, context->registeredClientCount, serviceCount); 194 195 // Notify any clients waiting on discovery completion 196 chppMutexLock(&context->discoveryMutex); 197 context->isDiscoveryComplete = true; 198 context->matchedClientCount = matchedClients; 199 context->discoveredServiceCount = serviceCount; 200 chppConditionVariableSignal(&context->discoveryCv); 201 chppMutexUnlock(&context->discoveryMutex); 202 203 // Notify clients of match 204 for (uint8_t i = 0; i < context->discoveredServiceCount; i++) { 205 uint8_t clientIndex = context->clientIndexOfServiceIndex[i]; 206 if (clientIndex != CHPP_CLIENT_INDEX_NONE) { 207 // Discovered service has a matched client 208 ChppNotifierFunction *MatchNotifierFunction = 209 chppGetClientMatchNotifierFunction(context, clientIndex); 210 211 CHPP_LOGD("Client #%" PRIu8 " (H#%d) match notifier found=%d", 212 clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i), 213 (MatchNotifierFunction != NULL)); 214 215 if (MatchNotifierFunction != NULL) { 216 MatchNotifierFunction(context->registeredClientContexts[clientIndex]); 217 } 218 } 219 } 220 } 221 222 /** 223 * Returns the match notification function pointer of a particular negotiated 224 * client. The function pointer will be set to null by clients that do not need 225 * or support a match notification. 226 * 227 * @param context Maintains status for each app layer instance. 228 * @param index Index of the registered client. 229 * 230 * @return Pointer to the match notification function. 231 */ 232 ChppNotifierFunction *chppGetClientMatchNotifierFunction( 233 struct ChppAppState *context, uint8_t index) { 234 return context->registeredClients[index]->matchNotifierFunctionPtr; 235 } 236 237 /************************************************ 238 * Public Functions 239 ***********************************************/ 240 241 void chppDiscoveryInit(struct ChppAppState *context) { 242 CHPP_ASSERT_LOG(!context->isDiscoveryClientInitialized, 243 "Discovery client already initialized"); 244 245 CHPP_LOGD("Initializing CHPP discovery client"); 246 247 if (!context->isDiscoveryClientInitialized) { 248 chppMutexInit(&context->discoveryMutex); 249 chppConditionVariableInit(&context->discoveryCv); 250 context->isDiscoveryClientInitialized = true; 251 } 252 253 context->matchedClientCount = 0; 254 context->isDiscoveryComplete = false; 255 context->isDiscoveryClientInitialized = true; 256 } 257 258 void chppDiscoveryDeinit(struct ChppAppState *context) { 259 CHPP_ASSERT_LOG(context->isDiscoveryClientInitialized, 260 "Discovery client already deinitialized"); 261 262 CHPP_LOGD("Deinitializing CHPP discovery client"); 263 context->isDiscoveryClientInitialized = false; 264 } 265 266 bool chppWaitForDiscoveryComplete(struct ChppAppState *context, 267 uint64_t timeoutMs) { 268 bool success = false; 269 270 if (!context->isDiscoveryClientInitialized) { 271 timeoutMs = 0; 272 } else { 273 success = true; 274 275 chppMutexLock(&context->discoveryMutex); 276 if (timeoutMs == 0) { 277 success = context->isDiscoveryComplete; 278 } else { 279 while (success && !context->isDiscoveryComplete) { 280 success = chppConditionVariableTimedWait( 281 &context->discoveryCv, &context->discoveryMutex, 282 timeoutMs * CHPP_NSEC_PER_MSEC); 283 } 284 } 285 chppMutexUnlock(&context->discoveryMutex); 286 } 287 288 if (!success) { 289 CHPP_LOGE("Discovery incomplete after %" PRIu64 " ms", timeoutMs); 290 } 291 return success; 292 } 293 294 bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *context, 295 const uint8_t *buf, size_t len) { 296 const struct ChppAppHeader *rxHeader = (const struct ChppAppHeader *)buf; 297 bool success = true; 298 299 switch (rxHeader->command) { 300 case CHPP_DISCOVERY_COMMAND_DISCOVER_ALL: { 301 chppDiscoveryProcessDiscoverAll(context, buf, len); 302 break; 303 } 304 default: { 305 success = false; 306 break; 307 } 308 } 309 return success; 310 } 311 312 void chppInitiateDiscovery(struct ChppAppState *context) { 313 if (context->isDiscoveryComplete) { 314 CHPP_LOGE("Duplicate discovery init"); 315 return; 316 } 317 318 for (uint8_t i = 0; i < CHPP_MAX_DISCOVERED_SERVICES; i++) { 319 context->clientIndexOfServiceIndex[i] = CHPP_CLIENT_INDEX_NONE; 320 } 321 322 struct ChppAppHeader *request = chppMalloc(sizeof(struct ChppAppHeader)); 323 request->handle = CHPP_HANDLE_DISCOVERY; 324 request->type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST; 325 request->transaction = 0; 326 request->error = CHPP_APP_ERROR_NONE; 327 request->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL; 328 329 chppEnqueueTxDatagramOrFail(context->transportContext, request, 330 sizeof(*request)); 331 } 332 333 bool chppAreAllClientsMatched(struct ChppAppState *context) { 334 bool success = false; 335 chppMutexLock(&context->discoveryMutex); 336 success = (context->isDiscoveryComplete) && 337 (context->registeredClientCount == context->matchedClientCount); 338 chppMutexUnlock(&context->discoveryMutex); 339 return success; 340 } 341