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