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  */
chppIsClientCompatibleWithService(const struct ChppClientDescriptor * client,const struct ChppServiceDescriptor * service)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  */
chppFindMatchingClient(struct ChppAppState * context,const struct ChppServiceDescriptor * service)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  */
chppDiscoveryProcessDiscoverAll(struct ChppAppState * context,const uint8_t * buf,size_t len)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  */
chppGetClientMatchNotifierFunction(struct ChppAppState * context,uint8_t index)232 ChppNotifierFunction *chppGetClientMatchNotifierFunction(
233     struct ChppAppState *context, uint8_t index) {
234   return context->registeredClients[index]->matchNotifierFunctionPtr;
235 }
236 
237 /************************************************
238  *  Public Functions
239  ***********************************************/
240 
chppDiscoveryInit(struct ChppAppState * context)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 
chppDiscoveryDeinit(struct ChppAppState * context)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 
chppWaitForDiscoveryComplete(struct ChppAppState * context,uint64_t timeoutMs)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 
chppDispatchDiscoveryServiceResponse(struct ChppAppState * context,const uint8_t * buf,size_t len)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 
chppInitiateDiscovery(struct ChppAppState * context)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 
chppAreAllClientsMatched(struct ChppAppState * context)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