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