• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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("Dupe discovery resp");
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   CHPP_DEBUG_ASSERT_LOG(
114       servicesLen == serviceCount * sizeof(struct ChppServiceDescriptor),
115       "Discovery desc len=%" PRIuSIZE " != count=%" PRIu8 " * size=%" PRIuSIZE,
116       servicesLen, serviceCount, sizeof(struct ChppServiceDescriptor));
117 
118   CHPP_DEBUG_ASSERT_LOG(serviceCount <= CHPP_MAX_DISCOVERED_SERVICES,
119                         "Service count=%" PRIu8 " > max=%d", serviceCount,
120                         CHPP_MAX_DISCOVERED_SERVICES);
121 
122   CHPP_LOGI("Discovered %" PRIu8 " services", serviceCount);
123 
124   uint8_t matchedClients = 0;
125   for (uint8_t i = 0; i < MIN(serviceCount, CHPP_MAX_DISCOVERED_SERVICES);
126        i++) {
127     // Update lookup table
128     context->clientIndexOfServiceIndex[i] =
129         chppFindMatchingClient(context, &response->services[i]);
130 
131     char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
132     chppUuidToStr(response->services[i].uuid, uuidText);
133 
134     if (context->clientIndexOfServiceIndex[i] == CHPP_CLIENT_INDEX_NONE) {
135       CHPP_LOGE(
136           "No client for service #%d"
137           " name=%s, UUID=%s, v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
138           CHPP_SERVICE_HANDLE_OF_INDEX(i), response->services[i].name, uuidText,
139           response->services[i].version.major,
140           response->services[i].version.minor,
141           response->services[i].version.patch);
142 
143     } else {
144       CHPP_LOGD(
145           "Client # %" PRIu8
146           " matched to service on handle %d"
147           " with name=%s, UUID=%s. "
148           "client version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
149           ", service version=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
150           context->clientIndexOfServiceIndex[i],
151           CHPP_SERVICE_HANDLE_OF_INDEX(i), response->services[i].name, uuidText,
152           context->registeredClients[context->clientIndexOfServiceIndex[i]]
153               ->descriptor.version.major,
154           context->registeredClients[context->clientIndexOfServiceIndex[i]]
155               ->descriptor.version.minor,
156           context->registeredClients[context->clientIndexOfServiceIndex[i]]
157               ->descriptor.version.patch,
158           response->services[i].version.major,
159           response->services[i].version.minor,
160           response->services[i].version.patch);
161 
162       // Initialize client
163       uint8_t idx = context->clientIndexOfServiceIndex[i];
164       if (context->registeredClients[idx]->initFunctionPtr(
165               context->registeredClientContexts[idx],
166               CHPP_SERVICE_HANDLE_OF_INDEX(i),
167               response->services[i].version) == false) {
168         CHPP_LOGE(
169             "Client v=%" PRIu8 ".%" PRIu8 ".%" PRIu16
170             " rejected init. Service v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
171             context->registeredClients[context->clientIndexOfServiceIndex[i]]
172                 ->descriptor.version.major,
173             context->registeredClients[context->clientIndexOfServiceIndex[i]]
174                 ->descriptor.version.minor,
175             context->registeredClients[context->clientIndexOfServiceIndex[i]]
176                 ->descriptor.version.patch,
177             response->services[i].version.major,
178             response->services[i].version.minor,
179             response->services[i].version.patch);
180       } else {
181         matchedClients++;
182       }
183     }
184   }
185 
186   CHPP_LOGD("Matched %" PRIu8 " out of %" PRIu8 " clients and %" PRIu8
187             " services",
188             matchedClients, context->registeredClientCount, serviceCount);
189 
190   // Notify any clients waiting on discovery completion
191   chppMutexLock(&context->discoveryMutex);
192   context->isDiscoveryComplete = true;
193   context->matchedClientCount = matchedClients;
194   context->discoveredServiceCount = serviceCount;
195   chppConditionVariableSignal(&context->discoveryCv);
196   chppMutexUnlock(&context->discoveryMutex);
197 
198   // Notify clients of match
199   for (uint8_t i = 0; i < context->discoveredServiceCount; i++) {
200     uint8_t clientIndex = context->clientIndexOfServiceIndex[i];
201     if (clientIndex != CHPP_CLIENT_INDEX_NONE) {
202       // Discovered service has a matched client
203       ChppNotifierFunction *MatchNotifierFunction =
204           chppGetClientMatchNotifierFunction(context, clientIndex);
205 
206       CHPP_LOGD("Client #%" PRIu8 " (H#%d) match notifier found=%d",
207                 clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i),
208                 (MatchNotifierFunction != NULL));
209 
210       if (MatchNotifierFunction != NULL) {
211         MatchNotifierFunction(context->registeredClientContexts[clientIndex]);
212       }
213     }
214   }
215 }
216 
217 /**
218  * Returns the match notification function pointer of a particular negotiated
219  * client. The function pointer will be set to null by clients that do not need
220  * or support a match notification.
221  *
222  * @param context Maintains status for each app layer instance.
223  * @param index Index of the registered client.
224  *
225  * @return Pointer to the match notification function.
226  */
chppGetClientMatchNotifierFunction(struct ChppAppState * context,uint8_t index)227 ChppNotifierFunction *chppGetClientMatchNotifierFunction(
228     struct ChppAppState *context, uint8_t index) {
229   return context->registeredClients[index]->matchNotifierFunctionPtr;
230 }
231 
232 /************************************************
233  *  Public Functions
234  ***********************************************/
235 
chppDiscoveryInit(struct ChppAppState * context)236 void chppDiscoveryInit(struct ChppAppState *context) {
237   CHPP_ASSERT_LOG(!context->isDiscoveryClientInitialized,
238                   "Discovery client already initialized");
239 
240   CHPP_LOGD("Initializing CHPP discovery client");
241 
242   if (!context->isDiscoveryClientInitialized) {
243     chppMutexInit(&context->discoveryMutex);
244     chppConditionVariableInit(&context->discoveryCv);
245     context->isDiscoveryClientInitialized = true;
246   }
247 
248   context->matchedClientCount = 0;
249   context->isDiscoveryComplete = false;
250   context->isDiscoveryClientInitialized = true;
251 }
252 
chppDiscoveryDeinit(struct ChppAppState * context)253 void chppDiscoveryDeinit(struct ChppAppState *context) {
254   CHPP_ASSERT_LOG(context->isDiscoveryClientInitialized,
255                   "Discovery client already deinitialized");
256 
257   CHPP_LOGD("Deinitializing CHPP discovery client");
258   context->isDiscoveryClientInitialized = false;
259 }
260 
chppWaitForDiscoveryComplete(struct ChppAppState * context,uint64_t timeoutMs)261 bool chppWaitForDiscoveryComplete(struct ChppAppState *context,
262                                   uint64_t timeoutMs) {
263   bool success = false;
264 
265   if (!context->isDiscoveryClientInitialized) {
266     timeoutMs = 0;
267   } else {
268     success = true;
269 
270     chppMutexLock(&context->discoveryMutex);
271     if (timeoutMs == 0) {
272       success = context->isDiscoveryComplete;
273     } else {
274       while (success && !context->isDiscoveryComplete) {
275         success = chppConditionVariableTimedWait(
276             &context->discoveryCv, &context->discoveryMutex,
277             timeoutMs * CHPP_NSEC_PER_MSEC);
278       }
279     }
280     chppMutexUnlock(&context->discoveryMutex);
281   }
282 
283   if (!success) {
284     CHPP_LOGE("Discovery incomplete after %" PRIu64 " ms", timeoutMs);
285   }
286   return success;
287 }
288 
chppDispatchDiscoveryServiceResponse(struct ChppAppState * context,const uint8_t * buf,size_t len)289 bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *context,
290                                           const uint8_t *buf, size_t len) {
291   const struct ChppAppHeader *rxHeader = (const struct ChppAppHeader *)buf;
292   bool success = true;
293 
294   switch (rxHeader->command) {
295     case CHPP_DISCOVERY_COMMAND_DISCOVER_ALL: {
296       chppDiscoveryProcessDiscoverAll(context, buf, len);
297       break;
298     }
299     default: {
300       success = false;
301       break;
302     }
303   }
304   return success;
305 }
306 
chppInitiateDiscovery(struct ChppAppState * context)307 void chppInitiateDiscovery(struct ChppAppState *context) {
308   if (context->isDiscoveryComplete) {
309     CHPP_LOGE("Duplicate discovery init");
310     return;
311   }
312 
313   for (uint8_t i = 0; i < CHPP_MAX_DISCOVERED_SERVICES; i++) {
314     context->clientIndexOfServiceIndex[i] = CHPP_CLIENT_INDEX_NONE;
315   }
316 
317   struct ChppAppHeader *request = chppMalloc(sizeof(struct ChppAppHeader));
318   request->handle = CHPP_HANDLE_DISCOVERY;
319   request->type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
320   request->transaction = 0;
321   request->error = CHPP_APP_ERROR_NONE;
322   request->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL;
323 
324   chppEnqueueTxDatagramOrFail(context->transportContext, request,
325                               sizeof(*request));
326 }
327 
chppAreAllClientsMatched(struct ChppAppState * context)328 bool chppAreAllClientsMatched(struct ChppAppState *context) {
329   bool success = false;
330   chppMutexLock(&context->discoveryMutex);
331   success = (context->isDiscoveryComplete) &&
332             (context->registeredClientCount == context->matchedClientCount);
333   chppMutexUnlock(&context->discoveryMutex);
334   return success;
335 }
336