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