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 chppFindMatchingClientIndex(
39 struct ChppAppState *appState, const struct ChppServiceDescriptor *service);
40 static void chppProcessDiscoverAllResponse(
41 struct ChppAppState *appState, const struct ChppDiscoveryResponse *response,
42 size_t responseLen);
43 static ChppNotifierFunction *chppGetClientMatchNotifierFunction(
44 struct ChppAppState *appState, uint8_t index);
45
46 /************************************************
47 * Private Functions
48 ***********************************************/
49
50 /**
51 * Determines if a client is compatible with a service.
52 *
53 * Compatibility requirements are:
54 * 1. UUIDs must match
55 * 2. Major version numbers must match
56 *
57 * @param client ChppClientDescriptor of client.
58 * @param service ChppServiceDescriptor of service.
59 *
60 * @param return True if compatible.
61 */
chppIsClientCompatibleWithService(const struct ChppClientDescriptor * client,const struct ChppServiceDescriptor * service)62 static inline bool chppIsClientCompatibleWithService(
63 const struct ChppClientDescriptor *client,
64 const struct ChppServiceDescriptor *service) {
65 return memcmp(client->uuid, service->uuid, CHPP_SERVICE_UUID_LEN) == 0 &&
66 client->version.major == service->version.major;
67 }
68
69 /**
70 * Matches a registered client to a (discovered) service.
71 *
72 * @param appState Application layer state.
73 * @param service ChppServiceDescriptor of service.
74 *
75 * @param return Index of client matching the service, or CHPP_CLIENT_INDEX_NONE
76 * if there is none.
77 */
chppFindMatchingClientIndex(struct ChppAppState * appState,const struct ChppServiceDescriptor * service)78 static uint8_t chppFindMatchingClientIndex(
79 struct ChppAppState *appState,
80 const struct ChppServiceDescriptor *service) {
81 uint8_t result = CHPP_CLIENT_INDEX_NONE;
82
83 const struct ChppClient **clients = appState->registeredClients;
84
85 for (uint8_t i = 0; i < appState->registeredClientCount; i++) {
86 if (chppIsClientCompatibleWithService(&clients[i]->descriptor, service)) {
87 result = i;
88 break;
89 }
90 }
91
92 return result;
93 }
94
95 /**
96 * Processes the Discover All Services response
97 * (CHPP_DISCOVERY_COMMAND_DISCOVER_ALL).
98 *
99 * @param appState Application layer state.
100 * @param response The response from the discovery service.
101 * @param responseLen Length of the in bytes.
102 */
chppProcessDiscoverAllResponse(struct ChppAppState * appState,const struct ChppDiscoveryResponse * response,size_t responseLen)103 static void chppProcessDiscoverAllResponse(
104 struct ChppAppState *appState, const struct ChppDiscoveryResponse *response,
105 size_t responseLen) {
106 if (appState->isDiscoveryComplete) {
107 CHPP_LOGE("Dupe discovery resp");
108 return;
109 }
110
111 size_t servicesLen = responseLen - sizeof(struct ChppAppHeader);
112 uint8_t serviceCount =
113 (uint8_t)(servicesLen / sizeof(struct ChppServiceDescriptor));
114
115 CHPP_DEBUG_ASSERT_LOG(
116 servicesLen == serviceCount * sizeof(struct ChppServiceDescriptor),
117 "Discovery desc len=%" PRIuSIZE " != count=%" PRIu8 " * size=%" PRIuSIZE,
118 servicesLen, serviceCount, sizeof(struct ChppServiceDescriptor));
119
120 CHPP_DEBUG_ASSERT_LOG(serviceCount <= CHPP_MAX_DISCOVERED_SERVICES,
121 "Service count=%" PRIu8 " > max=%d", serviceCount,
122 CHPP_MAX_DISCOVERED_SERVICES);
123
124 CHPP_LOGI("Discovered %" PRIu8 " services", serviceCount);
125
126 uint8_t matchedClients = 0;
127 for (uint8_t i = 0; i < MIN(serviceCount, CHPP_MAX_DISCOVERED_SERVICES);
128 i++) {
129 const struct ChppServiceDescriptor *service = &response->services[i];
130
131 // Update lookup table
132 uint8_t clientIndex = chppFindMatchingClientIndex(appState, service);
133 appState->clientIndexOfServiceIndex[i] = clientIndex;
134
135 char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
136 chppUuidToStr(service->uuid, uuidText);
137
138 if (clientIndex == CHPP_CLIENT_INDEX_NONE) {
139 CHPP_LOGE(
140 "No client for service #%d"
141 " name=%s, UUID=%s, v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
142 CHPP_SERVICE_HANDLE_OF_INDEX(i), service->name, uuidText,
143 service->version.major, service->version.minor,
144 service->version.patch);
145 continue;
146 }
147
148 const struct ChppClient *client = appState->registeredClients[clientIndex];
149
150 CHPP_LOGD("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 clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i), service->name,
156 uuidText, client->descriptor.version.major,
157 client->descriptor.version.minor,
158 client->descriptor.version.patch, service->version.major,
159 service->version.minor, service->version.patch);
160
161 // Initialize client
162 if (!client->initFunctionPtr(
163 appState->registeredClientContexts[clientIndex],
164 CHPP_SERVICE_HANDLE_OF_INDEX(i), service->version)) {
165 CHPP_LOGE("Client v=%" PRIu8 ".%" PRIu8 ".%" PRIu16
166 " rejected init. Service v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
167 client->descriptor.version.major,
168 client->descriptor.version.minor,
169 client->descriptor.version.patch, service->version.major,
170 service->version.minor, service->version.patch);
171 continue;
172 }
173
174 matchedClients++;
175 }
176
177 CHPP_LOGD("Matched %" PRIu8 " out of %" PRIu8 " clients and %" PRIu8
178 " services",
179 matchedClients, appState->registeredClientCount, serviceCount);
180
181 // Notify any clients waiting on discovery completion
182 chppMutexLock(&appState->discoveryMutex);
183 appState->isDiscoveryComplete = true;
184 appState->matchedClientCount = matchedClients;
185 appState->discoveredServiceCount = serviceCount;
186 chppConditionVariableSignal(&appState->discoveryCv);
187 chppMutexUnlock(&appState->discoveryMutex);
188
189 // Notify clients of match
190 for (uint8_t i = 0; i < appState->discoveredServiceCount; i++) {
191 uint8_t clientIndex = appState->clientIndexOfServiceIndex[i];
192 if (clientIndex != CHPP_CLIENT_INDEX_NONE) {
193 // Discovered service has a matched client
194 ChppNotifierFunction *matchNotifierFunction =
195 chppGetClientMatchNotifierFunction(appState, clientIndex);
196
197 CHPP_LOGD("Client #%" PRIu8 " (H#%d) match notifier found=%d",
198 clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i),
199 (matchNotifierFunction != NULL));
200
201 if (matchNotifierFunction != NULL) {
202 matchNotifierFunction(appState->registeredClientContexts[clientIndex]);
203 }
204 }
205 }
206 }
207
208 /**
209 * Returns the match notification function pointer of a particular negotiated
210 * client. The function pointer will be set to null by clients that do not need
211 * or support a match notification.
212 *
213 * @param appState Application layer state.
214 * @param index Index of the registered client.
215 *
216 * @return Pointer to the match notification function.
217 */
chppGetClientMatchNotifierFunction(struct ChppAppState * appState,uint8_t index)218 static ChppNotifierFunction *chppGetClientMatchNotifierFunction(
219 struct ChppAppState *appState, uint8_t index) {
220 return appState->registeredClients[index]->matchNotifierFunctionPtr;
221 }
222
223 /************************************************
224 * Public Functions
225 ***********************************************/
226
chppDiscoveryInit(struct ChppAppState * appState)227 void chppDiscoveryInit(struct ChppAppState *appState) {
228 CHPP_ASSERT_LOG(!appState->isDiscoveryClientInitialized,
229 "Discovery client already initialized");
230
231 CHPP_LOGD("Initializing CHPP discovery client");
232
233 if (!appState->isDiscoveryClientInitialized) {
234 chppMutexInit(&appState->discoveryMutex);
235 chppConditionVariableInit(&appState->discoveryCv);
236 appState->isDiscoveryClientInitialized = true;
237 }
238
239 appState->matchedClientCount = 0;
240 appState->isDiscoveryComplete = false;
241 appState->isDiscoveryClientInitialized = true;
242 }
243
chppDiscoveryDeinit(struct ChppAppState * appState)244 void chppDiscoveryDeinit(struct ChppAppState *appState) {
245 CHPP_ASSERT_LOG(appState->isDiscoveryClientInitialized,
246 "Discovery client already deinitialized");
247
248 CHPP_LOGD("Deinitializing CHPP discovery client");
249 appState->isDiscoveryClientInitialized = false;
250 }
251
chppWaitForDiscoveryComplete(struct ChppAppState * appState,uint64_t timeoutMs)252 bool chppWaitForDiscoveryComplete(struct ChppAppState *appState,
253 uint64_t timeoutMs) {
254 bool success = false;
255
256 if (!appState->isDiscoveryClientInitialized) {
257 timeoutMs = 0;
258 } else {
259 success = true;
260
261 chppMutexLock(&appState->discoveryMutex);
262 if (timeoutMs == 0) {
263 success = appState->isDiscoveryComplete;
264 } else {
265 while (success && !appState->isDiscoveryComplete) {
266 success = chppConditionVariableTimedWait(
267 &appState->discoveryCv, &appState->discoveryMutex,
268 timeoutMs * CHPP_NSEC_PER_MSEC);
269 }
270 }
271 chppMutexUnlock(&appState->discoveryMutex);
272 }
273
274 if (!success) {
275 CHPP_LOGE("Discovery incomplete after %" PRIu64 " ms", timeoutMs);
276 }
277 return success;
278 }
279
chppDispatchDiscoveryServiceResponse(struct ChppAppState * appState,const uint8_t * buf,size_t len)280 bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *appState,
281 const uint8_t *buf, size_t len) {
282 const struct ChppAppHeader *rxHeader = (const struct ChppAppHeader *)buf;
283 bool success = true;
284
285 switch (rxHeader->command) {
286 case CHPP_DISCOVERY_COMMAND_DISCOVER_ALL: {
287 chppProcessDiscoverAllResponse(
288 appState, (const struct ChppDiscoveryResponse *)buf, len);
289 break;
290 }
291 default: {
292 success = false;
293 break;
294 }
295 }
296 return success;
297 }
298
chppInitiateDiscovery(struct ChppAppState * appState)299 void chppInitiateDiscovery(struct ChppAppState *appState) {
300 if (appState->isDiscoveryComplete) {
301 CHPP_LOGE("Duplicate discovery init");
302 return;
303 }
304
305 for (uint8_t i = 0; i < CHPP_MAX_DISCOVERED_SERVICES; i++) {
306 appState->clientIndexOfServiceIndex[i] = CHPP_CLIENT_INDEX_NONE;
307 }
308
309 struct ChppAppHeader *request = chppMalloc(sizeof(struct ChppAppHeader));
310 request->handle = CHPP_HANDLE_DISCOVERY;
311 request->type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
312 request->transaction = 0;
313 request->error = CHPP_APP_ERROR_NONE;
314 request->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL;
315
316 chppEnqueueTxDatagramOrFail(appState->transportContext, request,
317 sizeof(*request));
318 }
319
chppAreAllClientsMatched(struct ChppAppState * appState)320 bool chppAreAllClientsMatched(struct ChppAppState *appState) {
321 bool success = false;
322 chppMutexLock(&appState->discoveryMutex);
323 success = (appState->isDiscoveryComplete) &&
324 (appState->registeredClientCount == appState->matchedClientCount);
325 chppMutexUnlock(&appState->discoveryMutex);
326 return success;
327 }
328