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/clients.h"
26 #include "chpp/common/discovery.h"
27 #include "chpp/log.h"
28 #include "chpp/macros.h"
29 #include "chpp/memory.h"
30 #include "chpp/transport.h"
31 
32 /************************************************
33  *  Prototypes
34  ***********************************************/
35 
36 static inline bool chppIsClientCompatibleWithService(
37     const struct ChppClientDescriptor *client,
38     const struct ChppServiceDescriptor *service);
39 static uint8_t chppFindMatchingClientIndex(
40     struct ChppAppState *appState, const struct ChppServiceDescriptor *service);
41 static void chppProcessDiscoverAllResponse(
42     struct ChppAppState *appState, const struct ChppDiscoveryResponse *response,
43     size_t responseLen);
44 static ChppNotifierFunction *chppGetClientMatchNotifierFunction(
45     struct ChppAppState *appState, uint8_t index);
46 
47 /************************************************
48  *  Private Functions
49  ***********************************************/
50 
51 /**
52  * Determines if a client is compatible with a service.
53  *
54  * Compatibility requirements are:
55  * 1. UUIDs must match
56  * 2. Major version numbers must match
57  *
58  * @param client ChppClientDescriptor of client.
59  * @param service ChppServiceDescriptor of service.
60  *
61  * @param return True if compatible.
62  */
chppIsClientCompatibleWithService(const struct ChppClientDescriptor * client,const struct ChppServiceDescriptor * service)63 static inline bool chppIsClientCompatibleWithService(
64     const struct ChppClientDescriptor *client,
65     const struct ChppServiceDescriptor *service) {
66   return memcmp(client->uuid, service->uuid, CHPP_SERVICE_UUID_LEN) == 0 &&
67          client->version.major == service->version.major;
68 }
69 
70 /**
71  * Matches a registered client to a (discovered) service.
72  *
73  * @param appState Application layer state.
74  * @param service ChppServiceDescriptor of service.
75  *
76  * @param return Index of client matching the service, or CHPP_CLIENT_INDEX_NONE
77  * if there is none.
78  */
chppFindMatchingClientIndex(struct ChppAppState * appState,const struct ChppServiceDescriptor * service)79 static uint8_t chppFindMatchingClientIndex(
80     struct ChppAppState *appState,
81     const struct ChppServiceDescriptor *service) {
82   uint8_t result = CHPP_CLIENT_INDEX_NONE;
83 
84   const struct ChppClient **clients = appState->registeredClients;
85 
86   for (uint8_t i = 0; i < appState->registeredClientCount; i++) {
87     if (chppIsClientCompatibleWithService(&clients[i]->descriptor, service)) {
88       result = i;
89       break;
90     }
91   }
92 
93   return result;
94 }
95 
96 /**
97  * Processes the Discover All Services response
98  * (CHPP_DISCOVERY_COMMAND_DISCOVER_ALL).
99  *
100  * @param appState Application layer state.
101  * @param response The response from the discovery service.
102  * @param responseLen Length of the in bytes.
103  */
chppProcessDiscoverAllResponse(struct ChppAppState * appState,const struct ChppDiscoveryResponse * response,size_t responseLen)104 static void chppProcessDiscoverAllResponse(
105     struct ChppAppState *appState, const struct ChppDiscoveryResponse *response,
106     size_t responseLen) {
107   if (appState->isDiscoveryComplete) {
108     CHPP_LOGE("Dupe discovery resp");
109     return;
110   }
111 
112   size_t servicesLen = responseLen - sizeof(struct ChppAppHeader);
113   uint8_t serviceCount =
114       (uint8_t)(servicesLen / sizeof(struct ChppServiceDescriptor));
115 
116   CHPP_DEBUG_ASSERT_LOG(
117       servicesLen == serviceCount * sizeof(struct ChppServiceDescriptor),
118       "Discovery desc len=%" PRIuSIZE " != count=%" PRIu8 " * size=%" PRIuSIZE,
119       servicesLen, serviceCount, sizeof(struct ChppServiceDescriptor));
120 
121   CHPP_DEBUG_ASSERT_LOG(serviceCount <= CHPP_MAX_DISCOVERED_SERVICES,
122                         "Service count=%" PRIu8 " > max=%d", serviceCount,
123                         CHPP_MAX_DISCOVERED_SERVICES);
124 
125   CHPP_LOGI("Discovered %" PRIu8 " services", serviceCount);
126 
127   uint8_t matchedClients = 0;
128   for (uint8_t i = 0; i < MIN(serviceCount, CHPP_MAX_DISCOVERED_SERVICES);
129        i++) {
130     const struct ChppServiceDescriptor *service = &response->services[i];
131 
132     // Update lookup table
133     uint8_t clientIndex = chppFindMatchingClientIndex(appState, service);
134     appState->clientIndexOfServiceIndex[i] = clientIndex;
135 
136     char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
137     chppUuidToStr(service->uuid, uuidText);
138 
139     if (clientIndex == CHPP_CLIENT_INDEX_NONE) {
140       CHPP_LOGE(
141           "No client for service #%d"
142           " name=%s, UUID=%s, v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
143           CHPP_SERVICE_HANDLE_OF_INDEX(i), service->name, uuidText,
144           service->version.major, service->version.minor,
145           service->version.patch);
146       continue;
147     }
148 
149     const struct ChppClient *client = appState->registeredClients[clientIndex];
150 
151     CHPP_LOGD("Client # %" PRIu8
152               " matched to service on handle %d"
153               " with name=%s, UUID=%s. "
154               "client version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
155               ", service version=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
156               clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i), service->name,
157               uuidText, client->descriptor.version.major,
158               client->descriptor.version.minor,
159               client->descriptor.version.patch, service->version.major,
160               service->version.minor, service->version.patch);
161 
162     // Initialize client
163     if (!client->initFunctionPtr(
164             appState->registeredClientStates[clientIndex]->context,
165             CHPP_SERVICE_HANDLE_OF_INDEX(i), service->version)) {
166       CHPP_LOGE("Client v=%" PRIu8 ".%" PRIu8 ".%" PRIu16
167                 " rejected init. Service v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
168                 client->descriptor.version.major,
169                 client->descriptor.version.minor,
170                 client->descriptor.version.patch, service->version.major,
171                 service->version.minor, service->version.patch);
172       continue;
173     }
174 
175     matchedClients++;
176   }
177 
178   CHPP_LOGD("Matched %" PRIu8 " out of %" PRIu8 " clients and %" PRIu8
179             " services",
180             matchedClients, appState->registeredClientCount, serviceCount);
181 
182   // Notify any clients waiting on discovery completion
183   chppMutexLock(&appState->discoveryMutex);
184   appState->isDiscoveryComplete = true;
185   appState->matchedClientCount = matchedClients;
186   appState->discoveredServiceCount = serviceCount;
187   chppConditionVariableSignal(&appState->discoveryCv);
188   chppMutexUnlock(&appState->discoveryMutex);
189 
190   // Notify clients of match
191   for (uint8_t i = 0; i < appState->discoveredServiceCount; i++) {
192     uint8_t clientIndex = appState->clientIndexOfServiceIndex[i];
193     if (clientIndex != CHPP_CLIENT_INDEX_NONE) {
194       // Discovered service has a matched client
195       ChppNotifierFunction *matchNotifierFunction =
196           chppGetClientMatchNotifierFunction(appState, clientIndex);
197 
198       CHPP_LOGD("Client #%" PRIu8 " (H#%d) match notifier found=%d",
199                 clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i),
200                 (matchNotifierFunction != NULL));
201 
202       if (matchNotifierFunction != NULL) {
203         matchNotifierFunction(
204             appState->registeredClientStates[clientIndex]->context);
205       }
206     }
207   }
208 }
209 
210 /**
211  * Returns the match notification function pointer of a particular negotiated
212  * client. The function pointer will be set to null by clients that do not need
213  * or support a match notification.
214  *
215  * @param appState Application layer state.
216  * @param index Index of the registered client.
217  *
218  * @return Pointer to the match notification function.
219  */
chppGetClientMatchNotifierFunction(struct ChppAppState * appState,uint8_t index)220 static ChppNotifierFunction *chppGetClientMatchNotifierFunction(
221     struct ChppAppState *appState, uint8_t index) {
222   return appState->registeredClients[index]->matchNotifierFunctionPtr;
223 }
224 
225 /************************************************
226  *  Public Functions
227  ***********************************************/
228 
chppDiscoveryInit(struct ChppAppState * appState)229 void chppDiscoveryInit(struct ChppAppState *appState) {
230   CHPP_ASSERT_LOG(!appState->isDiscoveryClientInitialized,
231                   "Discovery client already initialized");
232 
233   CHPP_LOGD("Initializing CHPP discovery client");
234 
235   if (!appState->isDiscoveryClientInitialized) {
236     chppMutexInit(&appState->discoveryMutex);
237     chppConditionVariableInit(&appState->discoveryCv);
238     appState->isDiscoveryClientInitialized = true;
239   }
240 
241   appState->matchedClientCount = 0;
242   appState->isDiscoveryComplete = false;
243   appState->isDiscoveryClientInitialized = true;
244 }
245 
chppDiscoveryDeinit(struct ChppAppState * appState)246 void chppDiscoveryDeinit(struct ChppAppState *appState) {
247   CHPP_ASSERT_LOG(appState->isDiscoveryClientInitialized,
248                   "Discovery client already deinitialized");
249 
250   CHPP_LOGD("Deinitializing CHPP discovery client");
251   appState->isDiscoveryClientInitialized = false;
252 }
253 
chppWaitForDiscoveryComplete(struct ChppAppState * appState,uint64_t timeoutMs)254 bool chppWaitForDiscoveryComplete(struct ChppAppState *appState,
255                                   uint64_t timeoutMs) {
256   bool success = false;
257 
258   if (!appState->isDiscoveryClientInitialized) {
259     timeoutMs = 0;
260   } else {
261     success = true;
262 
263     chppMutexLock(&appState->discoveryMutex);
264     if (timeoutMs == 0) {
265       success = appState->isDiscoveryComplete;
266     } else {
267       while (success && !appState->isDiscoveryComplete) {
268         success = chppConditionVariableTimedWait(
269             &appState->discoveryCv, &appState->discoveryMutex,
270             timeoutMs * CHPP_NSEC_PER_MSEC);
271       }
272     }
273     chppMutexUnlock(&appState->discoveryMutex);
274   }
275 
276   if (!success) {
277     CHPP_LOGE("Discovery incomplete after %" PRIu64 " ms", timeoutMs);
278   }
279   return success;
280 }
281 
chppDispatchDiscoveryServiceResponse(struct ChppAppState * appState,const uint8_t * buf,size_t len)282 bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *appState,
283                                           const uint8_t *buf, size_t len) {
284   const struct ChppAppHeader *rxHeader = (const struct ChppAppHeader *)buf;
285   bool success = true;
286 
287   switch (rxHeader->command) {
288     case CHPP_DISCOVERY_COMMAND_DISCOVER_ALL: {
289       chppProcessDiscoverAllResponse(
290           appState, (const struct ChppDiscoveryResponse *)buf, len);
291       break;
292     }
293     default: {
294       success = false;
295       break;
296     }
297   }
298   return success;
299 }
300 
chppInitiateDiscovery(struct ChppAppState * appState)301 void chppInitiateDiscovery(struct ChppAppState *appState) {
302   if (appState->isDiscoveryComplete) {
303     CHPP_LOGE("Duplicate discovery init");
304     return;
305   }
306 
307   for (uint8_t i = 0; i < CHPP_MAX_DISCOVERED_SERVICES; i++) {
308     appState->clientIndexOfServiceIndex[i] = CHPP_CLIENT_INDEX_NONE;
309   }
310 
311   struct ChppAppHeader *request = chppMalloc(sizeof(struct ChppAppHeader));
312   request->handle = CHPP_HANDLE_DISCOVERY;
313   request->type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
314   request->transaction = 0;
315   request->error = CHPP_APP_ERROR_NONE;
316   request->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL;
317 
318   chppEnqueueTxDatagramOrFail(appState->transportContext, request,
319                               sizeof(*request));
320 }
321 
chppAreAllClientsMatched(struct ChppAppState * appState)322 bool chppAreAllClientsMatched(struct ChppAppState *appState) {
323   bool success = false;
324   chppMutexLock(&appState->discoveryMutex);
325   success = (appState->isDiscoveryComplete) &&
326             (appState->registeredClientCount == appState->matchedClientCount);
327   chppMutexUnlock(&appState->discoveryMutex);
328   return success;
329 }
330