]> gitweb.ps.run Git - matrix_esp_thesis/blob - src/matrix.c
generate identity keys
[matrix_esp_thesis] / src / matrix.c
1 #include "matrix.h"\r
2 \r
3 #include <time.h>\r
4 #include <stdio.h>\r
5 #include <mjson.h>\r
6 \r
7 \r
8 #define LOGIN_REQUEST_SIZE 1024\r
9 #define LOGIN_RESPONSE_SIZE 1024\r
10 #define LOGIN_URL "/_matrix/client/v3/login"\r
11 \r
12 #define ENCRYPTED_REQUEST_SIZE 512\r
13 #define ENCRYPTED_EVENT_SIZE 1024\r
14 #define ROOMEVENT_REQUEST_SIZE 256\r
15 #define ROOMEVENT_RESPONSE_SIZE 1024\r
16 #define ROOMEVENT_URL "/_matrix/client/v3/rooms/%s/send/%s/%d"\r
17 \r
18 #define TODEVICE_EVENT_SIZE 512\r
19 #define TODEVICE_URL "/_matrix/client/v3/sendToDevice/%s/%d"\r
20 \r
21 #define KEYS_QUERY_URL "/_matrix/client/v3/keys/query"\r
22 #define KEYS_QUERY_REQUEST_SIZE 256\r
23 #define KEYS_QUERY_RESPONSE_SIZE 1024\r
24 \r
25 #define JSON_QUERY_SIZE 128\r
26 \r
27 \r
28 \r
29 void\r
30 Randomize(uint8_t * random, int randomLen)\r
31 {\r
32     static bool first = false;\r
33     if (first) { srand(time(0)); first = false; }\r
34 \r
35     for (int i = 0; i < randomLen; i++)\r
36     {\r
37         random[i] = rand() % 256;\r
38     }\r
39 }\r
40 \r
41 bool\r
42 JsonEscape(\r
43     char * sIn, int sInLen,\r
44     char * sOut, int sOutCap)\r
45 {\r
46     int sOutIndex = 0;\r
47 \r
48     for (int i = 0; i < sInLen; i++)\r
49     {\r
50         if (i >= sOutCap)\r
51             return false;\r
52         \r
53         if (sIn[i] == '.' ||\r
54             sIn[i] == '[' ||\r
55             sIn[i] == ']'\r
56         ) {\r
57             sOut[sOutIndex++] = '\\';\r
58         }\r
59         sOut[sOutIndex++] = sIn[i];\r
60     }\r
61 \r
62     if (sOutIndex < sOutCap)\r
63         sOut[sOutIndex] = '\0';\r
64 \r
65     return true;\r
66 }\r
67 \r
68 // TODO: in/outbound sessions\r
69 bool\r
70 MatrixOlmSessionInit(\r
71     MatrixOlmSession * session,\r
72     const char * deviceId)\r
73 {\r
74     memset(session, 0, sizeof(MatrixOlmSession));\r
75 \r
76     static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
77     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
78 \r
79     session->deviceId = deviceId;\r
80 \r
81     session->session =\r
82         olm_session(session->memory);\r
83 \r
84     return session->session != NULL;\r
85 }\r
86 \r
87 bool\r
88 MatrixOlmSessionEncrypt(\r
89     MatrixOlmSession * session,\r
90     const char * plaintext,\r
91     char * outBuffer, int outBufferCap)\r
92 {\r
93     static uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
94     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
95 \r
96     size_t res = olm_encrypt(session->session,\r
97         plaintext, strlen(plaintext),\r
98         random, OLM_ENCRYPT_RANDOM_SIZE,\r
99         outBuffer, outBufferCap);\r
100 \r
101     return res != olm_error();\r
102 }\r
103 \r
104 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session\r
105 bool\r
106 MatrixMegolmOutSessionInit(\r
107     MatrixMegolmOutSession * session,\r
108     const char * roomId)\r
109 {\r
110     memset(session, 0, sizeof(MatrixMegolmOutSession));\r
111 \r
112     static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
113     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
114 \r
115     session->roomId = roomId;\r
116 \r
117     session->session =\r
118         olm_outbound_group_session(session->memory);\r
119 \r
120     olm_init_outbound_group_session(\r
121         session->session,\r
122         random,\r
123         MEGOLM_INIT_RANDOM_SIZE);\r
124 \r
125     olm_outbound_group_session_id(session->session,\r
126         (uint8_t *)session->id,\r
127         MEGOLM_SESSION_ID_SIZE);\r
128         \r
129     olm_outbound_group_session_key(session->session,\r
130         (uint8_t *)session->key,\r
131         MEGOLM_SESSION_KEY_SIZE);\r
132     \r
133     return true;\r
134 }\r
135 \r
136 bool\r
137 MatrixMegolmOutSessionEncrypt(\r
138     MatrixMegolmOutSession * session,\r
139     const char * plaintext,\r
140     char * outBuffer, int outBufferCap)\r
141 {\r
142     size_t res = olm_group_encrypt(session->session,\r
143         (uint8_t *)plaintext, strlen(plaintext),\r
144         (uint8_t *)outBuffer, outBufferCap);\r
145 \r
146     return res != olm_error();\r
147 }\r
148 \r
149 \r
150 \r
151 bool\r
152 MatrixClientInit(\r
153     MatrixClient * client,\r
154     const char * server)\r
155 {\r
156     memset(client, 0, sizeof(MatrixClient));\r
157 \r
158     strcpy(client->server, server);\r
159 \r
160     // init olm account\r
161     client->olmAccount = olm_account(client->olmAccountMemory);\r
162 \r
163     static uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];\r
164     Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);\r
165 \r
166     size_t res;\r
167     res = olm_create_account(\r
168         client->olmAccount,\r
169         random,\r
170         OLM_ACCOUNT_RANDOM_SIZE);\r
171 \r
172     // set device key\r
173     static char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
174     res =\r
175         olm_account_identity_keys(\r
176             client->olmAccount,\r
177             deviceKeysJson,\r
178             OLM_IDENTITY_KEYS_JSON_SIZE);\r
179 \r
180     mjson_get_string(deviceKeysJson, res,\r
181         "$.curve25519",\r
182         client->deviceKey, DEVICE_KEY_SIZE);\r
183     mjson_get_string(deviceKeysJson, res,\r
184         "$.ed25519",\r
185         client->signingKey, SIGNING_KEY_SIZE);\r
186 \r
187     return true;\r
188 }\r
189 \r
190 bool\r
191 MatrixClientSetAccessToken(\r
192     MatrixClient * client,\r
193     const char * accessToken)\r
194 {\r
195     int accessTokenLen = strlen(accessToken);\r
196 \r
197     if (accessTokenLen > ACCESS_TOKEN_SIZE - 1)\r
198         return false;\r
199 \r
200     for (int i = 0; i < accessTokenLen; i++)\r
201         client->accessToken[i] = accessToken[i];\r
202 \r
203     return true;\r
204 }\r
205 \r
206 bool\r
207 MatrixClientSetDeviceId(\r
208     MatrixClient * client,\r
209     const char * deviceId)\r
210 {\r
211     int deviceIdLen = strlen(deviceId);\r
212 \r
213     if (deviceIdLen > DEVICE_ID_SIZE - 1)\r
214         return false;\r
215 \r
216     for (int i = 0; i < deviceIdLen; i++)\r
217         client->deviceId[i] = deviceId[i];\r
218 \r
219     return true;\r
220 }\r
221 \r
222 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login\r
223 bool\r
224 MatrixClientLoginPassword(\r
225     MatrixClient * client,\r
226     const char * username,\r
227     const char * password,\r
228     const char * displayName)\r
229 {\r
230     static char requestBuffer[LOGIN_REQUEST_SIZE];\r
231 \r
232     mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,\r
233         "{"\r
234             "\"type\": \"m.login.password\","\r
235             "\"identifier\": {"\r
236                 "\"type\": \"m.id.user\","\r
237                 "\"user\": \"%s\""\r
238             "},"\r
239             "\"password\": \"%s\","\r
240             "\"initial_device_display_name\": \"%s\""\r
241         "}",\r
242         username,\r
243         password,\r
244         displayName);\r
245     \r
246     static char responseBuffer[LOGIN_RESPONSE_SIZE];\r
247     bool result =\r
248         MatrixHttpPost(client,\r
249             LOGIN_URL,\r
250             requestBuffer,\r
251             responseBuffer, LOGIN_RESPONSE_SIZE,\r
252             false);\r
253     \r
254     int responseLen = strlen(responseBuffer);\r
255     \r
256     if (!result)\r
257         return false;\r
258 \r
259     mjson_get_string(responseBuffer, responseLen,\r
260         "$.access_token",\r
261         client->accessToken, ACCESS_TOKEN_SIZE);\r
262     mjson_get_string(responseBuffer, responseLen,\r
263         "$.device_id",\r
264         client->deviceId, DEVICE_ID_SIZE);\r
265     mjson_get_string(responseBuffer, responseLen,\r
266         "$.expires_in_ms",\r
267         client->expireMs, EXPIRE_MS_SIZE);\r
268     mjson_get_string(responseBuffer, responseLen,\r
269         "$.refresh_token",\r
270         client->refreshToken, REFRESH_TOKEN_SIZE);\r
271 \r
272     return true;\r
273 }\r
274 \r
275 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid\r
276 bool\r
277 MatrixClientSendEvent(\r
278     MatrixClient * client,\r
279     const char * roomId,\r
280     const char * msgType,\r
281     const char * msgBody)\r
282 {    \r
283     static char requestUrl[MAX_URL_LEN];\r
284     sprintf(requestUrl,\r
285         ROOMEVENT_URL, roomId, msgType, (int)time(NULL));\r
286 \r
287     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
288     bool result =\r
289         MatrixHttpPut(client,\r
290             requestUrl,\r
291             msgBody,\r
292             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
293             true);\r
294     \r
295     return result;\r
296 }\r
297 \r
298 // https://spec.matrix.org/v1.6/client-server-api/#mroomencrypted\r
299 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event\r
300 bool\r
301 MatrixClientSendEventEncrypted(\r
302     MatrixClient * client,\r
303     const char * roomId,\r
304     const char * msgType,\r
305     const char * msgBody)\r
306 {\r
307     // event json\r
308     static char requestBuffer[ROOMEVENT_REQUEST_SIZE];\r
309     sprintf(requestBuffer,\r
310         "{"\r
311         "\"type\":\"%s\","\r
312         "\"content\":%s,"\r
313         "\"room_id\":\"%s\""\r
314         "}",\r
315         msgType,\r
316         msgBody,\r
317         roomId);\r
318 \r
319     // get megolm session\r
320     MatrixMegolmOutSession * outSession;\r
321     MatrixClientGetMegolmOutSession(client, roomId, &outSession);\r
322         \r
323     // encrypt\r
324     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
325     MatrixMegolmOutSessionEncrypt(outSession,\r
326         requestBuffer,\r
327         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
328 \r
329     // encrypted event json\r
330     const char * senderKey = client->deviceKey;\r
331     const char * sessionId = outSession->id;\r
332     const char * deviceId = client->deviceId;\r
333 \r
334     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
335     sprintf(encryptedEventBuffer,\r
336         "{"\r
337         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
338         "\"sender_key\":\"%s\","\r
339         "\"ciphertext\":\"%s\","\r
340         "\"session_id\":\"%s\","\r
341         "\"device_id\":\"%s\""\r
342         "}",\r
343         senderKey,\r
344         encryptedBuffer,\r
345         sessionId,\r
346         deviceId);\r
347 \r
348     // send\r
349     return MatrixClientSendEvent(client,\r
350         roomId,\r
351         "m.room.encrypted",\r
352         encryptedEventBuffer);\r
353 }\r
354 \r
355 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync\r
356 bool\r
357 MatrixClientSync(\r
358     MatrixClient * client,\r
359     char * outSyncBuffer, int outSyncCap)\r
360 {\r
361     return\r
362         MatrixHttpGet(client,\r
363             "/_matrix/client/v3/sync",\r
364             outSyncBuffer, outSyncCap,\r
365             true);\r
366 }\r
367 \r
368 bool\r
369 MatrixClientShareMegolmOutSession(\r
370     MatrixClient * client,\r
371     const char * deviceId,\r
372     MatrixMegolmOutSession * session)\r
373 {\r
374     // generate room key event\r
375     char eventBuffer[KEY_SHARE_EVENT_LEN];\r
376     sprintf(eventBuffer,\r
377         "{"\r
378             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
379             "\"room_id\":\"%s\","\r
380             "\"session_id\":\"%s\","\r
381             "\"session_key\":\"%s\""\r
382         "}",\r
383         session->roomId,\r
384         session->id,\r
385         session->key\r
386     );\r
387 \r
388     // get olm session\r
389     MatrixOlmSession * olmSession;\r
390     MatrixClientGetOlmSession(client, deviceId, &olmSession);\r
391 \r
392     // encrypt\r
393     char encryptedBuffer[KEY_SHARE_EVENT_LEN];\r
394     MatrixOlmSessionEncrypt(olmSession,\r
395         eventBuffer,\r
396         encryptedBuffer, KEY_SHARE_EVENT_LEN);\r
397 \r
398     // send\r
399     MatrixClientSendToDeviceEncrypted(client,\r
400         client->userId,\r
401         deviceId,\r
402         encryptedBuffer,\r
403         "m.room_key");\r
404 \r
405     return true;\r
406 }\r
407 \r
408 // bool\r
409 // MatrixClientSetMegolmOutSession(\r
410 //     MatrixClient * client,\r
411 //     const char * roomId,\r
412 //     MatrixMegolmOutSession session)\r
413 // {\r
414 //     if (client->numMegolmOutSessions < 10)\r
415 //     {\r
416 //         session.roomId = roomId;\r
417 //         client->megolmOutSessions[client->numMegolmOutSessions] = session;\r
418 //         client->numMegolmOutSessions++;\r
419 \r
420 //         return true;\r
421 //     }\r
422 //     return false;\r
423 // }\r
424 \r
425 bool\r
426 MatrixClientGetMegolmOutSession(\r
427     MatrixClient * client,\r
428     const char * roomId,\r
429     MatrixMegolmOutSession ** outSession)\r
430 {\r
431     for (int i = 0; i < client->numMegolmOutSessions; i++)\r
432     {\r
433         if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)\r
434         {\r
435             *outSession = &client->megolmOutSessions[i];\r
436             return true;\r
437         }\r
438     }\r
439 \r
440     if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)\r
441     {\r
442         MatrixMegolmOutSessionInit(\r
443             &client->megolmOutSessions[client->numMegolmOutSessions],\r
444             roomId);\r
445 \r
446         *outSession = &client->megolmOutSessions[client->numMegolmOutSessions];\r
447         \r
448         client->numMegolmOutSessions++;\r
449 \r
450         return true;\r
451     }\r
452 \r
453     return false;\r
454 }\r
455 \r
456 bool\r
457 MatrixClientGetOlmSession(\r
458     MatrixClient * client,\r
459     const char * deviceId,\r
460     MatrixOlmSession ** outSession)\r
461 {\r
462     for (int i = 0; i < client->numOlmSessions; i++)\r
463     {\r
464         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
465         {\r
466             *outSession = &client->olmSessions[i];\r
467             return true;\r
468         }\r
469     }\r
470 \r
471     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
472     {\r
473         MatrixOlmSessionInit(\r
474             &client->olmSessions[client->numOlmSessions],\r
475             deviceId);\r
476 \r
477         *outSession = &client->olmSessions[client->numOlmSessions];\r
478         \r
479         client->numOlmSessions++;\r
480 \r
481         return true;\r
482     }\r
483 \r
484     return false;\r
485 }\r
486 \r
487 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid\r
488 bool\r
489 MatrixClientSendToDevice(\r
490     MatrixClient * client,\r
491     const char * userId,\r
492     const char * deviceId,\r
493     const char * message,\r
494     const char * msgType)\r
495 {\r
496     static char requestUrl[MAX_URL_LEN];\r
497     sprintf(requestUrl,\r
498         TODEVICE_URL, msgType, (int)time(NULL));\r
499 \r
500     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
501     snprintf(eventBuffer, TODEVICE_EVENT_SIZE,\r
502         "{"\r
503             "\"messages\": {"\r
504                 "\"%s\": {"\r
505                     "\"%s\":%s"\r
506                 "}"\r
507             "}"\r
508         "}",\r
509         userId,\r
510         deviceId,\r
511         message);\r
512 \r
513     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
514     bool result =\r
515         MatrixHttpPut(client,\r
516             requestUrl,\r
517             eventBuffer,\r
518             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
519             true);\r
520     \r
521     return result;\r
522 }\r
523 \r
524 bool\r
525 MatrixClientSendToDeviceEncrypted(\r
526     MatrixClient * client,\r
527     const char * userId,\r
528     const char * deviceId,\r
529     const char * message,\r
530     const char * msgType)\r
531 {\r
532     // get olm session\r
533     MatrixOlmSession * olmSession;\r
534     MatrixClientGetOlmSession(client, deviceId, &olmSession);\r
535 \r
536     // create event json\r
537     char deviceKey[DEVICE_KEY_SIZE];\r
538     MatrixClientGetDeviceKey(client, deviceId, deviceKey, DEVICE_KEY_SIZE);\r
539     const char * senderKey = client->deviceKey;\r
540     \r
541     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
542     sprintf(eventBuffer,\r
543         "{"\r
544         "\"type\": \"%s\","\r
545         "\"content\": \"%s\","\r
546         "\"sender\": \"%s\","\r
547         "\"recipient\": \"%s\","\r
548         "\"recipient_keys\": {"\r
549           "\"ed25519\": \"%s\""\r
550         "},"\r
551         "\"keys\": {"\r
552           "\"ed25519\": \"%s\""\r
553         "}"\r
554         "}",\r
555         msgType,\r
556         message,\r
557         client->userId,\r
558         userId, // recipient user id\r
559         deviceKey, // recipient device key\r
560         client->deviceKey);\r
561 \r
562     // encrypt\r
563     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
564     MatrixOlmSessionEncrypt(olmSession,\r
565         eventBuffer,\r
566         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
567 \r
568     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
569     sprintf(encryptedEventBuffer,\r
570         "{"\r
571         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
572         "\"sender_key\":\"%s\","\r
573         "\"ciphertext\":{"\r
574           "\"%s\":{"\r
575             "\"body\":\"%s\","\r
576             "\"type\":\"%d\""\r
577           "}"\r
578         "}"\r
579         "}",\r
580         senderKey,\r
581         deviceKey,\r
582         encryptedBuffer,\r
583         olmSession->type);\r
584 \r
585     // send\r
586     return MatrixClientSendToDevice(\r
587         client,\r
588         userId,\r
589         deviceId,\r
590         encryptedEventBuffer,\r
591         "m.room.encrypted");\r
592 }\r
593 \r
594 bool\r
595 MatrixClientFindDevice(\r
596     MatrixClient * client,\r
597     const char * deviceId,\r
598     MatrixDevice ** outDevice)\r
599 {\r
600     MatrixClientRequestDeviceKeys(client);\r
601 \r
602     for (int i = 0; i < client->numDevices; i++)\r
603     {\r
604         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
605         {\r
606             *outDevice = &client->devices[i];\r
607             return true;\r
608         }\r
609     }\r
610 \r
611     *outDevice = NULL;\r
612     return false;\r
613 }\r
614 \r
615 bool\r
616 MatrixClientGetDeviceKey(\r
617     MatrixClient * client,\r
618     const char * deviceId,\r
619     char * outDeviceKey, int outDeviceKeyCap)\r
620 {\r
621     MatrixClientRequestDeviceKeys(client);\r
622     \r
623     MatrixDevice * device;\r
624     if (MatrixClientFindDevice(client, deviceId, &device))\r
625     {\r
626         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
627         return true;\r
628     }\r
629 \r
630     return false;\r
631 }\r
632 \r
633 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery\r
634 bool\r
635 MatrixClientRequestDeviceKeys(\r
636     MatrixClient * client)\r
637 {\r
638     char userIdEscaped[USER_ID_SIZE];\r
639     JsonEscape(client->userId, strlen(client->userId),\r
640         userIdEscaped, USER_ID_SIZE);\r
641 \r
642     char request[KEYS_QUERY_REQUEST_SIZE];\r
643     snprintf(request, KEYS_QUERY_REQUEST_SIZE,\r
644         "{\"device_keys\":{\"%s\":[]}}", userIdEscaped);\r
645 \r
646     char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];\r
647     bool requestResult = MatrixHttpPost(client,\r
648         KEYS_QUERY_URL,\r
649         request,\r
650         responseBuffer, KEYS_QUERY_RESPONSE_SIZE,\r
651         true);\r
652 \r
653     if (requestResult)\r
654     {\r
655         // query for retrieving device keys for user id\r
656         char query[JSON_QUERY_SIZE];\r
657         snprintf(query, JSON_QUERY_SIZE,\r
658             "$.device_keys.%s", userIdEscaped);\r
659         \r
660         const char * s;\r
661         int slen;\r
662         mjson_find(responseBuffer, strlen(responseBuffer),\r
663             query, &s, &slen);\r
664 \r
665         // loop over keys\r
666         \r
667         int koff, klen, voff, vlen, vtype, off;\r
668         for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
669                                         &voff, &vlen, &vtype)) != 0; ) {\r
670             const char * key = s + koff;\r
671             const char * val = s + voff;\r
672 \r
673             // set device id, "key" is the JSON key\r
674             MatrixDevice d;\r
675             strncpy(d.deviceId, key, klen);\r
676 \r
677             // look for device key in value\r
678             char deviceKeyQuery[JSON_QUERY_SIZE];\r
679             snprintf(deviceKeyQuery, JSON_QUERY_SIZE,\r
680                 "$.keys.curve25519:%.*s", klen, key);\r
681             mjson_get_string(val, vlen,\r
682                 deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);\r
683 \r
684             // add device\r
685             if (client->numDevices < NUM_DEVICES)\r
686             {\r
687                 client->devices[client->numDevices] = d;\r
688                 client->numDevices++;\r
689             }\r
690             else\r
691             {\r
692                 return false;\r
693             }\r
694         }\r
695 \r
696         return true;\r
697     }\r
698     else\r
699     {\r
700         return false;\r
701     }\r
702 }\r