]> gitweb.ps.run Git - matrix_esp_thesis/blob - src/matrix.c
1f5ed2de70135ea20d2f215b8f251156380c3f34
[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 KEYS_UPLOAD_URL "/_matrix/client/v3/keys/upload"\r
26 #define KEYS_UPLOAD_REQUEST_SIZE 1024\r
27 #define KEYS_UPLOAD_REQUEST_SIGNED_SIZE 2048\r
28 #define KEYS_UPLOAD_RESPONSE_SIZE 2048\r
29 \r
30 #define JSON_QUERY_SIZE 128\r
31 \r
32 \r
33 \r
34 void\r
35 Randomize(\r
36     uint8_t * random,\r
37     int randomLen)\r
38 {\r
39     static bool first = false;\r
40     if (first) { srand(time(0)); first = false; }\r
41 \r
42     for (int i = 0; i < randomLen; i++)\r
43     {\r
44         random[i] = rand() % 256;\r
45     }\r
46 }\r
47 \r
48 bool\r
49 JsonEscape(\r
50     char * sIn, int sInLen,\r
51     char * sOut, int sOutCap)\r
52 {\r
53     int sOutIndex = 0;\r
54 \r
55     for (int i = 0; i < sInLen; i++)\r
56     {\r
57         if (i >= sOutCap)\r
58             return false;\r
59         \r
60         if (sIn[i] == '.' ||\r
61             sIn[i] == '[' ||\r
62             sIn[i] == ']'\r
63         ) {\r
64             sOut[sOutIndex++] = '\\';\r
65         }\r
66         sOut[sOutIndex++] = sIn[i];\r
67     }\r
68 \r
69     if (sOutIndex < sOutCap)\r
70         sOut[sOutIndex] = '\0';\r
71 \r
72     return true;\r
73 }\r
74 \r
75 bool JsonSign(\r
76     MatrixClient * client,\r
77     char * sIn, int sInLen,\r
78     char * sOut, int sOutCap)\r
79 {\r
80     static char signature[OLM_SIGNATURE_SIZE];\r
81     size_t res =\r
82         olm_account_sign(client->olmAccount.account,\r
83             sIn, sInLen,\r
84             signature, OLM_SIGNATURE_SIZE);\r
85     \r
86     int signatureLen = res;\r
87 \r
88     static char signatureJson[JSON_SIGNATURE_SIZE];\r
89     int signatureJsonLen =\r
90         mjson_snprintf(signatureJson, JSON_SIGNATURE_SIZE,\r
91             "{"\r
92                 "\"signatures\":{"\r
93                     "\"%s\":{"\r
94                         "\"ed25519:%s\":\"%.*s\""\r
95                     "}"\r
96                 "}"\r
97             "}",\r
98             client->userId,\r
99             client->deviceId,\r
100             signatureLen, signature);\r
101 \r
102     struct mjson_fixedbuf result = { sOut, sOutCap, 0 };\r
103     mjson_merge(\r
104         sIn, sInLen,\r
105         signatureJson, signatureJsonLen,\r
106         mjson_print_fixed_buf,\r
107         &result);\r
108 \r
109     return true;\r
110 }\r
111 \r
112 \r
113 bool\r
114 MatrixOlmAccountInit(\r
115     MatrixOlmAccount * account)\r
116 {\r
117     account->account = olm_account(account->memory);\r
118 \r
119     static uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];\r
120     Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);\r
121 \r
122     size_t res = olm_create_account(\r
123         account->account,\r
124         random,\r
125         OLM_ACCOUNT_RANDOM_SIZE);\r
126 \r
127     return res != olm_error();\r
128 }\r
129 \r
130 // TODO: in/outbound sessions\r
131 bool\r
132 MatrixOlmSessionInit(\r
133     MatrixOlmSession * session,\r
134     const char * deviceId)\r
135 {\r
136     memset(session, 0, sizeof(MatrixOlmSession));\r
137 \r
138     static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
139     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
140 \r
141     session->deviceId = deviceId;\r
142 \r
143     session->session =\r
144         olm_session(session->memory);\r
145 \r
146     return session->session != NULL;\r
147 }\r
148 \r
149 bool\r
150 MatrixOlmSessionEncrypt(\r
151     MatrixOlmSession * session,\r
152     const char * plaintext,\r
153     char * outBuffer, int outBufferCap)\r
154 {\r
155     static uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
156     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
157 \r
158     size_t res = olm_encrypt(session->session,\r
159         plaintext, strlen(plaintext),\r
160         random, OLM_ENCRYPT_RANDOM_SIZE,\r
161         outBuffer, outBufferCap);\r
162 \r
163     return res != olm_error();\r
164 }\r
165 \r
166 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session\r
167 bool\r
168 MatrixMegolmOutSessionInit(\r
169     MatrixMegolmOutSession * session,\r
170     const char * roomId)\r
171 {\r
172     memset(session, 0, sizeof(MatrixMegolmOutSession));\r
173 \r
174     static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
175     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
176 \r
177     session->roomId = roomId;\r
178 \r
179     session->session =\r
180         olm_outbound_group_session(session->memory);\r
181 \r
182     olm_init_outbound_group_session(\r
183         session->session,\r
184         random,\r
185         MEGOLM_INIT_RANDOM_SIZE);\r
186 \r
187     olm_outbound_group_session_id(session->session,\r
188         (uint8_t *)session->id,\r
189         MEGOLM_SESSION_ID_SIZE);\r
190         \r
191     olm_outbound_group_session_key(session->session,\r
192         (uint8_t *)session->key,\r
193         MEGOLM_SESSION_KEY_SIZE);\r
194     \r
195     return true;\r
196 }\r
197 \r
198 bool\r
199 MatrixMegolmOutSessionEncrypt(\r
200     MatrixMegolmOutSession * session,\r
201     const char * plaintext,\r
202     char * outBuffer, int outBufferCap)\r
203 {\r
204     size_t res = olm_group_encrypt(session->session,\r
205         (uint8_t *)plaintext, strlen(plaintext),\r
206         (uint8_t *)outBuffer, outBufferCap);\r
207 \r
208     return res != olm_error();\r
209 }\r
210 \r
211 \r
212 \r
213 bool\r
214 MatrixClientInit(\r
215     MatrixClient * client,\r
216     const char * server)\r
217 {\r
218     memset(client, 0, sizeof(MatrixClient));\r
219 \r
220     strcpy(client->server, server);\r
221 \r
222     // init olm account\r
223     MatrixOlmAccountInit(&client->olmAccount);\r
224 \r
225     // set device key\r
226     static char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
227     size_t res =\r
228         olm_account_identity_keys(\r
229             client->olmAccount.account,\r
230             deviceKeysJson,\r
231             OLM_IDENTITY_KEYS_JSON_SIZE);\r
232 \r
233     mjson_get_string(deviceKeysJson, res,\r
234         "$.curve25519",\r
235         client->deviceKey, DEVICE_KEY_SIZE);\r
236     mjson_get_string(deviceKeysJson, res,\r
237         "$.ed25519",\r
238         client->signingKey, SIGNING_KEY_SIZE);\r
239 \r
240     return true;\r
241 }\r
242 \r
243 bool\r
244 MatrixClientSetAccessToken(\r
245     MatrixClient * client,\r
246     const char * accessToken)\r
247 {\r
248     int accessTokenLen = strlen(accessToken);\r
249 \r
250     if (accessTokenLen > ACCESS_TOKEN_SIZE - 1)\r
251         return false;\r
252 \r
253     for (int i = 0; i < accessTokenLen; i++)\r
254         client->accessToken[i] = accessToken[i];\r
255 \r
256     return true;\r
257 }\r
258 \r
259 bool\r
260 MatrixClientSetDeviceId(\r
261     MatrixClient * client,\r
262     const char * deviceId)\r
263 {\r
264     int deviceIdLen = strlen(deviceId);\r
265 \r
266     if (deviceIdLen > DEVICE_ID_SIZE - 1)\r
267         return false;\r
268 \r
269     for (int i = 0; i < deviceIdLen; i++)\r
270         client->deviceId[i] = deviceId[i];\r
271 \r
272     return true;\r
273 }\r
274 \r
275 bool\r
276 MatrixClientSetUserId(\r
277     MatrixClient * client,\r
278     const char * userId)\r
279 {\r
280     int userIdLen = strlen(userId);\r
281 \r
282     if (userIdLen > USER_ID_SIZE - 1)\r
283         return false;\r
284 \r
285     for (int i = 0; i < userIdLen; i++)\r
286         client->userId[i] = userId[i];\r
287 \r
288     return true;\r
289 }\r
290 \r
291 bool\r
292 MatrixClientGenerateOnetimeKeys(\r
293     MatrixClient * client,\r
294     int numberOfKeys)\r
295 {\r
296     static uint8_t random[OLM_ONETIME_KEYS_RANDOM_SIZE];\r
297     Randomize(random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
298 \r
299     size_t res =\r
300         olm_account_generate_one_time_keys(client->olmAccount.account,\r
301             numberOfKeys, random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
302 \r
303     return res != olm_error();\r
304 }\r
305 \r
306 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
307 bool\r
308 MatrixClientUploadOnetimeKeys(\r
309     MatrixClient * client)\r
310 {\r
311     static char requestBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
312 \r
313     mjson_snprintf(requestBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
314         "{\"one_time_keys\":{");\r
315 \r
316     static char onetimeKeysBuffer[1024];\r
317     olm_account_one_time_keys(client->olmAccount.account,\r
318         onetimeKeysBuffer, 1024);\r
319 \r
320     const char *keys;\r
321     int keysLen;\r
322     mjson_find(onetimeKeysBuffer, strlen(onetimeKeysBuffer), "$.curve25519", &keys, &keysLen);\r
323 \r
324     int koff, klen, voff, vlen, vtype, off = 0;\r
325     while ((off = mjson_next(keys, keysLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0) {\r
326         static char keyJson[JSON_ONETIME_KEY_SIZE];\r
327         \r
328         snprintf(keyJson, JSON_ONETIME_KEY_SIZE,\r
329             "{\"key\":\"%.*s\"}",\r
330             vlen-2, keys + voff+1);\r
331 \r
332         static char keyJsonSigned[JSON_ONETIME_KEY_SIGNED_SIZE];\r
333 \r
334         JsonSign(client,\r
335             keyJson, JSON_ONETIME_KEY_SIZE,\r
336             keyJsonSigned, JSON_ONETIME_KEY_SIGNED_SIZE);\r
337         \r
338         mjson_snprintf(requestBuffer+strlen(requestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(requestBuffer),\r
339             "\"signed_curve25519:%.*s\":%s,",\r
340             klen-2, keys + koff+1,\r
341             keyJsonSigned);\r
342     }\r
343 \r
344     mjson_snprintf(requestBuffer+strlen(requestBuffer)-1, KEYS_UPLOAD_REQUEST_SIZE-strlen(requestBuffer),\r
345         "}}");\r
346 \r
347     static char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
348     MatrixHttpPost(client,\r
349         KEYS_UPLOAD_URL,\r
350         requestBuffer,\r
351         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
352         true);\r
353 \r
354     return true;\r
355 }\r
356 \r
357 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
358 bool\r
359 MatrixClientUploadDeviceKeys(\r
360     MatrixClient * client)\r
361 {\r
362     static char deviceKeysBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
363 \r
364     mjson_snprintf(deviceKeysBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
365         "{\"device_keys\":{"\r
366             "\"algorithms\":[\"m.olm.v1.curve25519-aes-sha2\",\"m.megolm.v1.aes-sha2\"],"\r
367             "\"device_id\":\"%s\","\r
368             "\"keys\":{"\r
369                 "\"curve25519:%s\":\"%s\","\r
370                 "\"ed25519:%s\":\"%s\""\r
371             "},"\r
372             "\"user_id\":\"%s\""\r
373         "}}",\r
374         client->deviceId,\r
375         client->deviceId, client->deviceKey,\r
376         client->deviceId, client->signingKey,\r
377         client->userId);\r
378 \r
379     static char deviceKeysSignedBuffer[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
380     JsonSign(client,\r
381         deviceKeysBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
382         deviceKeysSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);\r
383 \r
384 \r
385     static char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
386     MatrixHttpPost(client,\r
387         KEYS_UPLOAD_URL,\r
388         deviceKeysSignedBuffer,\r
389         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
390         true);\r
391 \r
392     return true;\r
393 }\r
394 \r
395 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login\r
396 bool\r
397 MatrixClientLoginPassword(\r
398     MatrixClient * client,\r
399     const char * username,\r
400     const char * password,\r
401     const char * displayName)\r
402 {\r
403     static char requestBuffer[LOGIN_REQUEST_SIZE];\r
404 \r
405     mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,\r
406         "{"\r
407             "\"type\": \"m.login.password\","\r
408             "\"identifier\": {"\r
409                 "\"type\": \"m.id.user\","\r
410                 "\"user\": \"%s\""\r
411             "},"\r
412             "\"password\": \"%s\","\r
413             "\"initial_device_display_name\": \"%s\""\r
414         "}",\r
415         username,\r
416         password,\r
417         displayName);\r
418     \r
419     static char responseBuffer[LOGIN_RESPONSE_SIZE];\r
420     bool result =\r
421         MatrixHttpPost(client,\r
422             LOGIN_URL,\r
423             requestBuffer,\r
424             responseBuffer, LOGIN_RESPONSE_SIZE,\r
425             false);\r
426     \r
427     int responseLen = strlen(responseBuffer);\r
428     \r
429     if (!result)\r
430         return false;\r
431 \r
432     mjson_get_string(responseBuffer, responseLen,\r
433         "$.access_token",\r
434         client->accessToken, ACCESS_TOKEN_SIZE);\r
435     mjson_get_string(responseBuffer, responseLen,\r
436         "$.device_id",\r
437         client->deviceId, DEVICE_ID_SIZE);\r
438     mjson_get_string(responseBuffer, responseLen,\r
439         "$.expires_in_ms",\r
440         client->expireMs, EXPIRE_MS_SIZE);\r
441     mjson_get_string(responseBuffer, responseLen,\r
442         "$.refresh_token",\r
443         client->refreshToken, REFRESH_TOKEN_SIZE);\r
444 \r
445     return true;\r
446 }\r
447 \r
448 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid\r
449 bool\r
450 MatrixClientSendEvent(\r
451     MatrixClient * client,\r
452     const char * roomId,\r
453     const char * msgType,\r
454     const char * msgBody)\r
455 {    \r
456     static char requestUrl[MAX_URL_LEN];\r
457     sprintf(requestUrl,\r
458         ROOMEVENT_URL, roomId, msgType, (int)time(NULL));\r
459 \r
460     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
461     bool result =\r
462         MatrixHttpPut(client,\r
463             requestUrl,\r
464             msgBody,\r
465             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
466             true);\r
467     \r
468     return result;\r
469 }\r
470 \r
471 // https://spec.matrix.org/v1.6/client-server-api/#mroomencrypted\r
472 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event\r
473 bool\r
474 MatrixClientSendEventEncrypted(\r
475     MatrixClient * client,\r
476     const char * roomId,\r
477     const char * msgType,\r
478     const char * msgBody)\r
479 {\r
480     // event json\r
481     static char requestBuffer[ROOMEVENT_REQUEST_SIZE];\r
482     sprintf(requestBuffer,\r
483         "{"\r
484         "\"type\":\"%s\","\r
485         "\"content\":%s,"\r
486         "\"room_id\":\"%s\""\r
487         "}",\r
488         msgType,\r
489         msgBody,\r
490         roomId);\r
491 \r
492     // get megolm session\r
493     MatrixMegolmOutSession * outSession;\r
494     MatrixClientGetMegolmOutSession(client, roomId, &outSession);\r
495         \r
496     // encrypt\r
497     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
498     MatrixMegolmOutSessionEncrypt(outSession,\r
499         requestBuffer,\r
500         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
501 \r
502     // encrypted event json\r
503     const char * senderKey = client->deviceKey;\r
504     const char * sessionId = outSession->id;\r
505     const char * deviceId = client->deviceId;\r
506 \r
507     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
508     sprintf(encryptedEventBuffer,\r
509         "{"\r
510         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
511         "\"sender_key\":\"%s\","\r
512         "\"ciphertext\":\"%s\","\r
513         "\"session_id\":\"%s\","\r
514         "\"device_id\":\"%s\""\r
515         "}",\r
516         senderKey,\r
517         encryptedBuffer,\r
518         sessionId,\r
519         deviceId);\r
520 \r
521     // send\r
522     return MatrixClientSendEvent(client,\r
523         roomId,\r
524         "m.room.encrypted",\r
525         encryptedEventBuffer);\r
526 }\r
527 \r
528 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync\r
529 bool\r
530 MatrixClientSync(\r
531     MatrixClient * client,\r
532     char * outSyncBuffer, int outSyncCap)\r
533 {\r
534     return\r
535         MatrixHttpGet(client,\r
536             "/_matrix/client/v3/sync",\r
537             outSyncBuffer, outSyncCap,\r
538             true);\r
539 }\r
540 \r
541 bool\r
542 MatrixClientShareMegolmOutSession(\r
543     MatrixClient * client,\r
544     const char * deviceId,\r
545     MatrixMegolmOutSession * session)\r
546 {\r
547     // generate room key event\r
548     char eventBuffer[KEY_SHARE_EVENT_LEN];\r
549     sprintf(eventBuffer,\r
550         "{"\r
551             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
552             "\"room_id\":\"%s\","\r
553             "\"session_id\":\"%s\","\r
554             "\"session_key\":\"%s\""\r
555         "}",\r
556         session->roomId,\r
557         session->id,\r
558         session->key\r
559     );\r
560 \r
561     // get olm session\r
562     MatrixOlmSession * olmSession;\r
563     MatrixClientGetOlmSession(client, deviceId, &olmSession);\r
564 \r
565     // encrypt\r
566     char encryptedBuffer[KEY_SHARE_EVENT_LEN];\r
567     MatrixOlmSessionEncrypt(olmSession,\r
568         eventBuffer,\r
569         encryptedBuffer, KEY_SHARE_EVENT_LEN);\r
570 \r
571     // send\r
572     MatrixClientSendToDeviceEncrypted(client,\r
573         client->userId,\r
574         deviceId,\r
575         encryptedBuffer,\r
576         "m.room_key");\r
577 \r
578     return true;\r
579 }\r
580 \r
581 bool\r
582 MatrixClientShareMegolmOutSessionTest(\r
583     MatrixClient * client,\r
584     const char * deviceId,\r
585     MatrixMegolmOutSession * session)\r
586 {\r
587     // generate room key event\r
588     char eventBuffer[KEY_SHARE_EVENT_LEN];\r
589     sprintf(eventBuffer,\r
590         "{"\r
591             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
592             "\"room_id\":\"%s\","\r
593             "\"session_id\":\"%s\","\r
594             "\"session_key\":\"%s\""\r
595         "}",\r
596         session->roomId,\r
597         session->id,\r
598         session->key\r
599     );\r
600 \r
601     // send\r
602     MatrixClientSendToDevice(client,\r
603         client->userId,\r
604         deviceId,\r
605         eventBuffer,\r
606         "m.room_key");\r
607 \r
608     return true;\r
609 }\r
610 \r
611 // bool\r
612 // MatrixClientSetMegolmOutSession(\r
613 //     MatrixClient * client,\r
614 //     const char * roomId,\r
615 //     MatrixMegolmOutSession session)\r
616 // {\r
617 //     if (client->numMegolmOutSessions < 10)\r
618 //     {\r
619 //         session.roomId = roomId;\r
620 //         client->megolmOutSessions[client->numMegolmOutSessions] = session;\r
621 //         client->numMegolmOutSessions++;\r
622 \r
623 //         return true;\r
624 //     }\r
625 //     return false;\r
626 // }\r
627 \r
628 bool\r
629 MatrixClientGetMegolmOutSession(\r
630     MatrixClient * client,\r
631     const char * roomId,\r
632     MatrixMegolmOutSession ** outSession)\r
633 {\r
634     for (int i = 0; i < client->numMegolmOutSessions; i++)\r
635     {\r
636         if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)\r
637         {\r
638             *outSession = &client->megolmOutSessions[i];\r
639             return true;\r
640         }\r
641     }\r
642 \r
643     if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)\r
644     {\r
645         MatrixMegolmOutSessionInit(\r
646             &client->megolmOutSessions[client->numMegolmOutSessions],\r
647             roomId);\r
648 \r
649         *outSession = &client->megolmOutSessions[client->numMegolmOutSessions];\r
650         \r
651         client->numMegolmOutSessions++;\r
652 \r
653         return true;\r
654     }\r
655 \r
656     return false;\r
657 }\r
658 \r
659 bool\r
660 MatrixClientGetOlmSession(\r
661     MatrixClient * client,\r
662     const char * deviceId,\r
663     MatrixOlmSession ** outSession)\r
664 {\r
665     for (int i = 0; i < client->numOlmSessions; i++)\r
666     {\r
667         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
668         {\r
669             *outSession = &client->olmSessions[i];\r
670             return true;\r
671         }\r
672     }\r
673 \r
674     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
675     {\r
676         MatrixOlmSessionInit(\r
677             &client->olmSessions[client->numOlmSessions],\r
678             deviceId);\r
679 \r
680         *outSession = &client->olmSessions[client->numOlmSessions];\r
681         \r
682         client->numOlmSessions++;\r
683 \r
684         return true;\r
685     }\r
686 \r
687     return false;\r
688 }\r
689 \r
690 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid\r
691 bool\r
692 MatrixClientSendToDevice(\r
693     MatrixClient * client,\r
694     const char * userId,\r
695     const char * deviceId,\r
696     const char * message,\r
697     const char * msgType)\r
698 {\r
699     static char requestUrl[MAX_URL_LEN];\r
700     sprintf(requestUrl,\r
701         TODEVICE_URL, msgType, (int)time(NULL));\r
702 \r
703     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
704     snprintf(eventBuffer, TODEVICE_EVENT_SIZE,\r
705         "{"\r
706             "\"messages\": {"\r
707                 "\"%s\": {"\r
708                     "\"%s\":%s"\r
709                 "}"\r
710             "}"\r
711         "}",\r
712         userId,\r
713         deviceId,\r
714         message);\r
715 \r
716     static char responseBuffer[ROOMEVENT_RESPONSE_SIZE];\r
717     bool result =\r
718         MatrixHttpPut(client,\r
719             requestUrl,\r
720             eventBuffer,\r
721             responseBuffer, ROOMEVENT_RESPONSE_SIZE,\r
722             true);\r
723     \r
724     return result;\r
725 }\r
726 \r
727 bool\r
728 MatrixClientSendToDeviceEncrypted(\r
729     MatrixClient * client,\r
730     const char * userId,\r
731     const char * deviceId,\r
732     const char * message,\r
733     const char * msgType)\r
734 {\r
735     // get olm session\r
736     MatrixOlmSession * olmSession;\r
737     MatrixClientGetOlmSession(client, deviceId, &olmSession);\r
738 \r
739     // create event json\r
740     char deviceKey[DEVICE_KEY_SIZE];\r
741     MatrixClientGetDeviceKey(client, deviceId, deviceKey, DEVICE_KEY_SIZE);\r
742     const char * senderKey = client->deviceKey;\r
743     \r
744     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
745     sprintf(eventBuffer,\r
746         "{"\r
747         "\"type\": \"%s\","\r
748         "\"content\": \"%s\","\r
749         "\"sender\": \"%s\","\r
750         "\"recipient\": \"%s\","\r
751         "\"recipient_keys\": {"\r
752           "\"ed25519\": \"%s\""\r
753         "},"\r
754         "\"keys\": {"\r
755           "\"ed25519\": \"%s\""\r
756         "}"\r
757         "}",\r
758         msgType,\r
759         message,\r
760         client->userId,\r
761         userId, // recipient user id\r
762         deviceKey, // recipient device key\r
763         client->deviceKey);\r
764 \r
765     // encrypt\r
766     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
767     MatrixOlmSessionEncrypt(olmSession,\r
768         eventBuffer,\r
769         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
770 \r
771     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
772     sprintf(encryptedEventBuffer,\r
773         "{"\r
774         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
775         "\"sender_key\":\"%s\","\r
776         "\"ciphertext\":{"\r
777           "\"%s\":{"\r
778             "\"body\":\"%s\","\r
779             "\"type\":\"%d\""\r
780           "}"\r
781         "}"\r
782         "}",\r
783         senderKey,\r
784         deviceKey,\r
785         encryptedBuffer,\r
786         olmSession->type);\r
787 \r
788     // send\r
789     return MatrixClientSendToDevice(\r
790         client,\r
791         userId,\r
792         deviceId,\r
793         encryptedEventBuffer,\r
794         "m.room.encrypted");\r
795 }\r
796 \r
797 bool\r
798 MatrixClientFindDevice(\r
799     MatrixClient * client,\r
800     const char * deviceId,\r
801     MatrixDevice ** outDevice)\r
802 {\r
803     MatrixClientRequestDeviceKeys(client);\r
804 \r
805     for (int i = 0; i < client->numDevices; i++)\r
806     {\r
807         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
808         {\r
809             *outDevice = &client->devices[i];\r
810             return true;\r
811         }\r
812     }\r
813 \r
814     *outDevice = NULL;\r
815     return false;\r
816 }\r
817 \r
818 bool\r
819 MatrixClientGetDeviceKey(\r
820     MatrixClient * client,\r
821     const char * deviceId,\r
822     char * outDeviceKey, int outDeviceKeyCap)\r
823 {\r
824     MatrixClientRequestDeviceKeys(client);\r
825     \r
826     MatrixDevice * device;\r
827     if (MatrixClientFindDevice(client, deviceId, &device))\r
828     {\r
829         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
830         return true;\r
831     }\r
832 \r
833     return false;\r
834 }\r
835 \r
836 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery\r
837 bool\r
838 MatrixClientRequestDeviceKeys(\r
839     MatrixClient * client)\r
840 {\r
841     char userIdEscaped[USER_ID_SIZE];\r
842     JsonEscape(client->userId, strlen(client->userId),\r
843         userIdEscaped, USER_ID_SIZE);\r
844 \r
845     char request[KEYS_QUERY_REQUEST_SIZE];\r
846     snprintf(request, KEYS_QUERY_REQUEST_SIZE,\r
847         "{\"device_keys\":{\"%s\":[]}}", userIdEscaped);\r
848 \r
849     char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];\r
850     bool requestResult = MatrixHttpPost(client,\r
851         KEYS_QUERY_URL,\r
852         request,\r
853         responseBuffer, KEYS_QUERY_RESPONSE_SIZE,\r
854         true);\r
855 \r
856     if (requestResult)\r
857     {\r
858         // query for retrieving device keys for user id\r
859         char query[JSON_QUERY_SIZE];\r
860         snprintf(query, JSON_QUERY_SIZE,\r
861             "$.device_keys.%s", userIdEscaped);\r
862         \r
863         const char * s;\r
864         int slen;\r
865         mjson_find(responseBuffer, strlen(responseBuffer),\r
866             query, &s, &slen);\r
867 \r
868         // loop over keys\r
869         \r
870         int koff, klen, voff, vlen, vtype, off;\r
871         for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
872                                         &voff, &vlen, &vtype)) != 0; ) {\r
873             const char * key = s + koff;\r
874             const char * val = s + voff;\r
875 \r
876             // set device id, "key" is the JSON key\r
877             MatrixDevice d;\r
878             strncpy(d.deviceId, key, klen);\r
879 \r
880             // look for device key in value\r
881             char deviceKeyQuery[JSON_QUERY_SIZE];\r
882             snprintf(deviceKeyQuery, JSON_QUERY_SIZE,\r
883                 "$.keys.curve25519:%.*s", klen, key);\r
884             mjson_get_string(val, vlen,\r
885                 deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);\r
886 \r
887             // add device\r
888             if (client->numDevices < NUM_DEVICES)\r
889             {\r
890                 client->devices[client->numDevices] = d;\r
891                 client->numDevices++;\r
892             }\r
893             else\r
894             {\r
895                 return false;\r
896             }\r
897         }\r
898 \r
899         return true;\r
900     }\r
901     else\r
902     {\r
903         return false;\r
904     }\r
905 }\r