]> gitweb.ps.run Git - matrix_esp_thesis/blob - src/matrix.c
Readme
[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 #include <olm/sas.h>\r
7 \r
8 #ifdef ESP_PLATFORM\r
9 #include <esp_random.h>\r
10 #endif\r
11 \r
12 #define STATIC static\r
13 \r
14 #define LOGIN_REQUEST_SIZE 1024\r
15 #define LOGIN_RESPONSE_SIZE 1024\r
16 #define LOGIN_URL "/_matrix/client/v3/login"\r
17 \r
18 #define ENCRYPTED_REQUEST_SIZE (1024*5)\r
19 STATIC char g_EncryptedRequestBuffer[ENCRYPTED_REQUEST_SIZE];\r
20 #define ENCRYPTED_EVENT_SIZE (1024*10)\r
21 STATIC char g_EncryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
22 #define ROOM_SEND_REQUEST_SIZE 256\r
23 #define ROOM_SEND_RESPONSE_SIZE 1024\r
24 #define ROOM_SEND_URL "/_matrix/client/v3/rooms/%s/send/%s/%d"\r
25 \r
26 #define ROOMKEY_REQUEST_SIZE (1024*4)\r
27 \r
28 #define TODEVICE_EVENT_SIZE (1024*5)\r
29 STATIC char g_TodeviceEventBuffer[TODEVICE_EVENT_SIZE];\r
30 #define TODEVICE_URL "/_matrix/client/v3/sendToDevice/%s/%d"\r
31 \r
32 #define KEYS_QUERY_URL "/_matrix/client/v3/keys/query"\r
33 #define KEYS_QUERY_REQUEST_SIZE 256\r
34 #define KEYS_QUERY_RESPONSE_SIZE (1024*5)\r
35 \r
36 #define KEYS_UPLOAD_URL "/_matrix/client/v3/keys/upload"\r
37 #define KEYS_UPLOAD_REQUEST_SIZE 1024*4\r
38 STATIC char g_KeysUploadRequestBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
39 #define KEYS_UPLOAD_REQUEST_SIGNED_SIZE 2048*4\r
40 STATIC char g_KeysUploadRequestSignedBuffer[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
41 #define KEYS_UPLOAD_RESPONSE_SIZE 2048\r
42 \r
43 #define KEYS_CLAIM_URL "/_matrix/client/v3/keys/claim"\r
44 #define KEYS_CLAIM_REQUEST_SIZE 1024\r
45 #define KEYS_CLAIM_RESPONSE_SIZE 1024\r
46 \r
47 #define SYNC_TIMEOUT 5000\r
48 \r
49 #define JSON_QUERY_SIZE 128\r
50 #define JSON_MAX_INDICES 100\r
51 #define JSON_MAX_ENTRY_SIZE 1024\r
52 \r
53 #define MAX(a,b) ((a) > (b) ? (a) : (b))\r
54 #define MIN(a,b) ((a) < (b) ? (a) : (b))\r
55 \r
56 void\r
57 Randomize(\r
58     uint8_t * random,\r
59     int randomLen)\r
60 {\r
61     #ifdef ESP_PLATFORM\r
62 \r
63     for (int i = 0; i < randomLen; i++)\r
64     {\r
65         random[i] = esp_random() % 256;\r
66     }\r
67 \r
68     #else\r
69 \r
70     STATIC bool first = true;\r
71     if (first) { srand(time(0)); first = false; }\r
72 \r
73     for (int i = 0; i < randomLen; i++)\r
74     {\r
75         random[i] = rand() % 256;\r
76     }\r
77 \r
78     #endif\r
79 }\r
80 \r
81 bool\r
82 JsonEscape(\r
83     const char * sIn, int sInLen,\r
84     char * sOut, int sOutCap)\r
85 {\r
86     int sOutIndex = 0;\r
87 \r
88     for (int i = 0; i < sInLen; i++)\r
89     {\r
90         if (i >= sOutCap)\r
91             return false;\r
92         \r
93         if (sIn[i] == '.' ||\r
94             sIn[i] == '[' ||\r
95             sIn[i] == ']'\r
96         ) {\r
97             sOut[sOutIndex++] = '\\';\r
98         }\r
99         sOut[sOutIndex++] = sIn[i];\r
100     }\r
101 \r
102     if (sOutIndex < sOutCap)\r
103         sOut[sOutIndex] = '\0';\r
104 \r
105     return true;\r
106 }\r
107 \r
108 bool\r
109 JsonCanonicalize(\r
110     const char * sIn, int sInLen,\r
111     char * sOut, int sOutCap)\r
112 {\r
113     snprintf(sOut, sOutCap, "{}");\r
114 \r
115     int koff, klen, voff, vlen, vtype, off;\r
116 \r
117     struct Key {\r
118         const char * ptr;\r
119         int len;\r
120     };\r
121 \r
122     struct Key keys[JSON_MAX_INDICES];\r
123     int numKeys = 0;\r
124 \r
125     for (off = 0; (off = mjson_next(sIn, sInLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0; ) {\r
126         keys[numKeys].ptr = sIn + koff;\r
127         keys[numKeys].len = klen;\r
128         numKeys++;\r
129     }\r
130 \r
131     for (int i = 0; i < numKeys; i++) {\r
132         for (int j = i; j < numKeys; j++) {\r
133             if (\r
134                 strncmp(\r
135                     keys[i].ptr,\r
136                     keys[j].ptr,\r
137                     MIN(keys[i].len, keys[j].len)\r
138                 ) > 0\r
139             ) {\r
140                 struct Key k = keys[i];\r
141                 keys[i] = keys[j];\r
142                 keys[j] = k;\r
143             }\r
144         }\r
145     }\r
146 \r
147     for (int i = 0; i < numKeys; i++) {\r
148         char jp[JSON_QUERY_SIZE];\r
149         snprintf(jp, JSON_QUERY_SIZE, "$.%.*s", keys[i].len-2, keys[i].ptr+1);\r
150 \r
151         const char * valPtr;\r
152         int valLen;\r
153         mjson_find(sIn, sInLen, jp, &valPtr, &valLen);\r
154         \r
155         STATIC char newEntry[JSON_MAX_ENTRY_SIZE];\r
156         snprintf(newEntry, JSON_MAX_ENTRY_SIZE, "{%.*s:%.*s}", keys[i].len, keys[i].ptr, valLen, valPtr);\r
157 \r
158         char * buffer = strdup(sOut);\r
159 \r
160         struct mjson_fixedbuf fb = { sOut, sOutCap, 0 };\r
161         mjson_merge(buffer, strlen(buffer), newEntry, strlen(newEntry), mjson_print_fixed_buf, &fb);\r
162 \r
163         free(buffer);\r
164     }\r
165 \r
166     // TODO: recursively sort entries\r
167 \r
168     return true;\r
169 }\r
170 \r
171 bool JsonSign(\r
172     MatrixClient * client,\r
173     const char * sIn, int sInLen,\r
174     char * sOut, int sOutCap)\r
175 {\r
176     STATIC char signature[OLM_SIGNATURE_SIZE];\r
177     size_t res =\r
178         olm_account_sign(client->olmAccount.account,\r
179             sIn, sInLen,\r
180             signature, OLM_SIGNATURE_SIZE);\r
181     \r
182     int signatureLen = res;\r
183     \r
184     STATIC char thisSigningKey[SIGNING_KEY_SIZE];\r
185     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, SIGNING_KEY_SIZE);\r
186 \r
187     STATIC char signatureJson[JSON_SIGNATURE_SIZE];\r
188     int signatureJsonLen =\r
189         mjson_snprintf(signatureJson, JSON_SIGNATURE_SIZE,\r
190             "{"\r
191                 "\"signatures\":{"\r
192                     "\"%s\":{"\r
193                         "\"ed25519:%s\":\"%.*s\""\r
194                     "}"\r
195                 "}"\r
196             "}",\r
197             client->userId,\r
198             //"1",\r
199             client->deviceId,\r
200             signatureLen, signature);\r
201 \r
202     struct mjson_fixedbuf result = { sOut, sOutCap, 0 };\r
203     mjson_merge(\r
204         sIn, sInLen,\r
205         signatureJson, signatureJsonLen,\r
206         mjson_print_fixed_buf,\r
207         &result);\r
208 \r
209     return true;\r
210 }\r
211 \r
212 \r
213 bool\r
214 MatrixOlmAccountInit(\r
215     MatrixOlmAccount * account)\r
216 {\r
217     account->account = olm_account(account->memory);\r
218 \r
219     STATIC uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];\r
220     Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);\r
221 \r
222     size_t res = olm_create_account(\r
223         account->account,\r
224         random,\r
225         OLM_ACCOUNT_RANDOM_SIZE);\r
226 \r
227     return res != olm_error();\r
228 }\r
229 \r
230 bool\r
231 MatrixOlmAccountUnpickle(\r
232     MatrixOlmAccount * account,\r
233     void * pickled, int pickledLen,\r
234     const void * key, int keyLen)\r
235 {\r
236     size_t res;\r
237     res = olm_unpickle_account(account->account,\r
238         key, keyLen,\r
239         pickled, pickledLen);\r
240     if (res == olm_error()) {\r
241         printf("error unpickling olm account:%s\n",\r
242             olm_account_last_error(account->account));\r
243     }\r
244     return res != olm_error();\r
245 }\r
246 \r
247 bool\r
248 MatrixOlmAccountGetDeviceKey(\r
249     MatrixOlmAccount * account,\r
250     char * key, int keyCap)\r
251 {\r
252     STATIC char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
253     size_t res =\r
254         olm_account_identity_keys(account->account,\r
255             deviceKeysJson, OLM_IDENTITY_KEYS_JSON_SIZE);\r
256     mjson_get_string(deviceKeysJson, res,\r
257         "$.curve25519",\r
258         key, keyCap);\r
259     return true;\r
260 }\r
261 \r
262 bool\r
263 MatrixOlmAccountGetSigningKey(\r
264     MatrixOlmAccount * account,\r
265     char * key, int keyCap)\r
266 {\r
267     STATIC char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
268     size_t res =\r
269         olm_account_identity_keys(account->account,\r
270             deviceKeysJson, OLM_IDENTITY_KEYS_JSON_SIZE);\r
271     mjson_get_string(deviceKeysJson, res,\r
272         "$.ed25519",\r
273         key, keyCap);\r
274     return true;\r
275 }\r
276 \r
277 bool\r
278 MatrixOlmSessionFrom(\r
279     MatrixOlmSession * session,\r
280     OlmAccount * olmAccount,\r
281     const char * deviceId,\r
282     const char * deviceKey,\r
283     const char * encrypted)\r
284 {\r
285     memset(session, 0, sizeof(MatrixOlmSession));\r
286 \r
287     session->deviceId = deviceId;\r
288 \r
289     session->session =\r
290         olm_session(session->memory);\r
291     \r
292     char * encryptedCopy = strdup(encrypted);\r
293     \r
294     size_t res =\r
295         olm_create_inbound_session_from(session->session, olmAccount,\r
296             deviceKey, strlen(deviceKey),\r
297             encryptedCopy, strlen(encryptedCopy));\r
298     \r
299     if (res == olm_error()) {\r
300         printf("error olm:%s\n", olm_session_last_error(session->session));\r
301     }\r
302 \r
303     return res != olm_error();\r
304 }\r
305 \r
306 bool\r
307 MatrixOlmSessionTo(\r
308     MatrixOlmSession * session,\r
309     OlmAccount * olmAccount,\r
310     const char * deviceId,\r
311     const char * deviceKey,\r
312     const char * deviceOnetimeKey)\r
313 {\r
314     memset(session, 0, sizeof(MatrixOlmSession));\r
315 \r
316     session->deviceId = deviceId;\r
317 \r
318     session->session =\r
319         olm_session(session->memory);\r
320 \r
321     STATIC uint8_t random[OLM_OUTBOUND_SESSION_RANDOM_SIZE];\r
322     Randomize(random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);\r
323 \r
324     size_t res =\r
325         olm_create_outbound_session(session->session,\r
326             olmAccount,\r
327             deviceKey, strlen(deviceKey),\r
328             deviceOnetimeKey, strlen(deviceOnetimeKey),\r
329             random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);\r
330     \r
331     if (res == olm_error()) {\r
332         printf("error olm:%s\n", olm_session_last_error(session->session));\r
333     }\r
334 \r
335     return res != olm_error();\r
336 }\r
337 \r
338 bool\r
339 MatrixOlmSessionUnpickle(\r
340     MatrixOlmSession * session,\r
341     const char * deviceId,\r
342     void * pickled, int pickledLen,\r
343     const void * key, int keyLen)\r
344 {\r
345     memset(session, 0, sizeof(MatrixOlmSession));\r
346 \r
347     session->deviceId = deviceId;\r
348 \r
349     session->session =\r
350         olm_session(session->memory);\r
351     \r
352     size_t res;\r
353     res = olm_unpickle_session(session->session,\r
354         key, keyLen,\r
355         pickled, pickledLen);\r
356     \r
357     if (res == olm_error()) {\r
358         printf("error unpickling olm session:%s\n", olm_session_last_error(session->session));\r
359     }\r
360 \r
361     return res != olm_error();\r
362 }\r
363 \r
364 bool\r
365 MatrixOlmSessionEncrypt(\r
366     MatrixOlmSession * session,\r
367     const char * plaintext,\r
368     char * outBuffer, int outBufferCap)\r
369 {\r
370     STATIC uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
371     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
372 \r
373     memset(outBuffer, 0, outBufferCap);\r
374     size_t res = olm_encrypt(session->session,\r
375         plaintext, strlen(plaintext),\r
376         random, OLM_ENCRYPT_RANDOM_SIZE,\r
377         outBuffer, outBufferCap);\r
378 \r
379     return res != olm_error();\r
380 }\r
381 \r
382 bool\r
383 MatrixOlmSessionDecrypt(\r
384     MatrixOlmSession * session,\r
385     size_t messageType,\r
386     char * encrypted,\r
387     char * outBuffer, int outBufferCap)\r
388 {\r
389     STATIC uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
390     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
391 \r
392     size_t res =\r
393         olm_decrypt(session->session,\r
394             messageType,\r
395             encrypted, strlen(encrypted),\r
396             outBuffer, outBufferCap);\r
397     \r
398     if (res != olm_error() && (int)res < outBufferCap)\r
399         outBuffer[res] = '\0';\r
400 \r
401     return res != olm_error();\r
402 }\r
403 \r
404 bool\r
405 MatrixMegolmInSessionInit(\r
406     MatrixMegolmInSession * session,\r
407     const char * roomId,\r
408     const char * sessionId,\r
409     const char * sessionKey, int sessionKeyLen)\r
410 {\r
411     memset(session, 0, sizeof(MatrixMegolmInSession));\r
412     \r
413     strncpy(session->roomId, roomId, sizeof(session->roomId));\r
414     strncpy(session->id, sessionId, sizeof(session->id));\r
415     strncpy(session->key, sessionKey, sizeof(session->key));\r
416 \r
417     session->session =\r
418         olm_inbound_group_session(session->memory);\r
419 \r
420     size_t res =\r
421         olm_init_inbound_group_session(\r
422         // olm_import_inbound_group_session(\r
423             session->session,\r
424             (const uint8_t *)sessionKey, sessionKeyLen);\r
425     if (res == olm_error()) {\r
426         printf("Error initializing Megolm session: %s\n", olm_inbound_group_session_last_error(session->session));\r
427     }\r
428 \r
429     return res != olm_error();\r
430 }\r
431 \r
432 bool\r
433 MatrixMegolmInSessionDecrypt(\r
434     MatrixMegolmInSession * session,\r
435     const char * encrypted, int encryptedLen,\r
436     char * outDecrypted, int outDecryptedCap)\r
437 {\r
438     // uint8_t buffer[1024];\r
439     // memcpy(buffer, encrypted, encryptedLen);\r
440 \r
441     uint32_t megolmInMessageIndex;\r
442 \r
443     size_t res =\r
444         olm_group_decrypt(session->session,\r
445             (uint8_t *)encrypted, encryptedLen,\r
446             (uint8_t *)outDecrypted, outDecryptedCap,\r
447             &megolmInMessageIndex);\r
448     \r
449     printf("message index: %d\n", (int)megolmInMessageIndex);\r
450     \r
451     if (res == olm_error()) {\r
452         printf("error decrypting megolm message: %s\n", olm_inbound_group_session_last_error(session->session));\r
453     }\r
454     else {\r
455         printf("decrypted len: %d\n", res);\r
456     }\r
457     \r
458     return true;\r
459 }\r
460 \r
461 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session\r
462 bool\r
463 MatrixMegolmOutSessionInit(\r
464     MatrixMegolmOutSession * session,\r
465     const char * roomId)\r
466 {\r
467     memset(session, 0, sizeof(MatrixMegolmOutSession));\r
468 \r
469     STATIC uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
470     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
471 \r
472     strncpy(session->roomId, roomId, ROOM_ID_SIZE);\r
473 \r
474     session->session =\r
475         olm_outbound_group_session(session->memory);\r
476 \r
477     olm_init_outbound_group_session(\r
478         session->session,\r
479         random,\r
480         MEGOLM_INIT_RANDOM_SIZE);\r
481 \r
482     olm_outbound_group_session_id(session->session,\r
483         (uint8_t *)session->id,\r
484         MEGOLM_SESSION_ID_SIZE);\r
485         \r
486     olm_outbound_group_session_key(session->session,\r
487         (uint8_t *)session->key,\r
488         MEGOLM_SESSION_KEY_SIZE);\r
489     \r
490     return true;\r
491 }\r
492 \r
493 bool\r
494 MatrixMegolmOutSessionEncrypt(\r
495     MatrixMegolmOutSession * session,\r
496     const char * plaintext,\r
497     char * outBuffer, int outBufferCap)\r
498 {\r
499     memset(outBuffer, 0, outBufferCap);\r
500     size_t res = olm_group_encrypt(session->session,\r
501         (uint8_t *)plaintext, strlen(plaintext),\r
502         (uint8_t *)outBuffer, outBufferCap);\r
503 \r
504     return res != olm_error();\r
505 }\r
506 \r
507 \r
508 bool\r
509 MatrixClientInit(\r
510     MatrixClient * client)\r
511 {\r
512     memset(client, 0, sizeof(MatrixClient));\r
513 \r
514     // init olm account\r
515     MatrixOlmAccountInit(&client->olmAccount);\r
516 \r
517     return true;\r
518 }\r
519 \r
520 bool\r
521 MatrixClientSetAccessToken(\r
522     MatrixClient * client,\r
523     const char * accessToken)\r
524 {\r
525     for (int i = 0; i < ACCESS_TOKEN_SIZE-1; i++)\r
526         client->accessToken[i] = accessToken[i];\r
527     client->accessToken[ACCESS_TOKEN_SIZE-1] = '\0';\r
528 \r
529     return true;\r
530 }\r
531 \r
532 bool\r
533 MatrixClientSetDeviceId(\r
534     MatrixClient * client,\r
535     const char * deviceId)\r
536 {\r
537     for (int i = 0; i < DEVICE_ID_SIZE-1; i++)\r
538         client->deviceId[i] = deviceId[i];\r
539     client->deviceId[DEVICE_ID_SIZE-1] = '\0';\r
540 \r
541     return true;\r
542 }\r
543 \r
544 bool\r
545 MatrixClientSetUserId(\r
546     MatrixClient * client,\r
547     const char * userId)\r
548 {\r
549     for (int i = 0; i < USER_ID_SIZE-1; i++)\r
550         client->userId[i] = userId[i];\r
551     client->userId[USER_ID_SIZE-1] = '\0';\r
552 \r
553     return true;\r
554 }\r
555 \r
556 bool\r
557 MatrixClientGenerateOnetimeKeys(\r
558     MatrixClient * client,\r
559     int numberOfKeys)\r
560 {\r
561     STATIC uint8_t random[OLM_ONETIME_KEYS_RANDOM_SIZE];\r
562     Randomize(random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
563 \r
564     size_t res =\r
565         olm_account_generate_one_time_keys(client->olmAccount.account,\r
566             numberOfKeys, random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
567 \r
568     return res != olm_error();\r
569 }\r
570 \r
571 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
572 bool\r
573 MatrixClientUploadOnetimeKeys(\r
574     MatrixClient * client)\r
575 {\r
576     mjson_snprintf(g_KeysUploadRequestBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
577         "{");\r
578 \r
579     STATIC char onetimeKeysBuffer[1024];\r
580     olm_account_one_time_keys(client->olmAccount.account,\r
581         onetimeKeysBuffer, 1024);\r
582 \r
583     const char *keys;\r
584     int keysLen;\r
585     mjson_find(onetimeKeysBuffer, strlen(onetimeKeysBuffer), "$.curve25519", &keys, &keysLen);\r
586 \r
587     int koff, klen, voff, vlen, vtype, off = 0;\r
588     while ((off = mjson_next(keys, keysLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0) {\r
589         STATIC char keyJson[JSON_ONETIME_KEY_SIZE];\r
590         \r
591         int keyJsonLen =\r
592             snprintf(keyJson, JSON_ONETIME_KEY_SIZE,\r
593                 "{\"key\":\"%.*s\"}",\r
594                 vlen-2, keys + voff+1);\r
595 \r
596         STATIC char keyJsonSigned[JSON_ONETIME_KEY_SIGNED_SIZE];\r
597 \r
598         JsonSign(client,\r
599             keyJson, keyJsonLen,\r
600             keyJsonSigned, JSON_ONETIME_KEY_SIGNED_SIZE);\r
601         \r
602         mjson_snprintf(g_KeysUploadRequestBuffer+strlen(g_KeysUploadRequestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(g_KeysUploadRequestBuffer),\r
603             "\"signed_curve25519:%.*s\":%s,",\r
604             klen-2, keys + koff+1,\r
605             keyJsonSigned);\r
606     }\r
607 \r
608     if (g_KeysUploadRequestBuffer[strlen(g_KeysUploadRequestBuffer)-1] == ',')\r
609         g_KeysUploadRequestBuffer[strlen(g_KeysUploadRequestBuffer)-1] = '\0';\r
610 \r
611     mjson_snprintf(g_KeysUploadRequestBuffer+strlen(g_KeysUploadRequestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(g_KeysUploadRequestBuffer),\r
612         "}");\r
613         \r
614     // STATIC char onetimeKeysSignedBuffer[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
615     // JsonSign(client,\r
616     //     g_KeysUploadRequestBuffer, strlen(g_KeysUploadRequestBuffer),\r
617     //     onetimeKeysSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);\r
618         \r
619     // STATIC char finalEvent[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
620     // snprintf(finalEvent, KEYS_UPLOAD_REQUEST_SIGNED_SIZE,\r
621     // "{\"one_time_keys\":%s}", onetimeKeysSignedBuffer);\r
622     snprintf(g_KeysUploadRequestSignedBuffer, KEYS_UPLOAD_REQUEST_SIGNED_SIZE,\r
623     "{\"one_time_keys\":%s}", g_KeysUploadRequestBuffer);\r
624 \r
625     STATIC char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
626     MatrixHttpPost(client->hc,\r
627         KEYS_UPLOAD_URL,\r
628         g_KeysUploadRequestSignedBuffer,\r
629         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
630         true);\r
631 \r
632     return true;\r
633 }\r
634 \r
635 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
636 bool\r
637 MatrixClientUploadDeviceKeys(\r
638     MatrixClient * client)\r
639 {\r
640     char thisDeviceKey[DEVICE_KEY_SIZE];\r
641     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
642     char thisSigningKey[DEVICE_KEY_SIZE];\r
643     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);\r
644 \r
645     int deviceKeysBufferLen =\r
646         mjson_snprintf(g_KeysUploadRequestBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
647             "{"\r
648                 "\"algorithms\":[\"m.olm.v1.curve25519-aes-sha2\",\"m.megolm.v1.aes-sha2\"],"\r
649                 "\"device_id\":\"%s\","\r
650                 "\"keys\":{"\r
651                     "\"curve25519:%s\":\"%s\","\r
652                     "\"ed25519:%s\":\"%s\""\r
653                 "},"\r
654                 "\"user_id\":\"%s\""\r
655             "}",\r
656             client->deviceId,\r
657             client->deviceId, thisDeviceKey,\r
658             client->deviceId, thisSigningKey,\r
659             client->userId);\r
660 \r
661     JsonSign(client,\r
662         g_KeysUploadRequestBuffer, deviceKeysBufferLen,\r
663         g_KeysUploadRequestSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);\r
664     \r
665     STATIC char finalEvent[KEYS_UPLOAD_REQUEST_SIGNED_SIZE+30];\r
666     snprintf(finalEvent, KEYS_UPLOAD_REQUEST_SIGNED_SIZE+30,\r
667     "{\"device_keys\":%s}", g_KeysUploadRequestSignedBuffer);\r
668 \r
669     STATIC char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
670     MatrixHttpPost(client->hc,\r
671         KEYS_UPLOAD_URL,\r
672         finalEvent,\r
673         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
674         true);\r
675 \r
676     return true;\r
677 }\r
678 \r
679 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysclaim\r
680 bool\r
681 MatrixClientClaimOnetimeKey(\r
682     MatrixClient * client,\r
683     const char * userId,\r
684     const char * deviceId,\r
685     char * outOnetimeKey, int outOnetimeKeyCap)\r
686 {\r
687     STATIC char requestBuffer[KEYS_CLAIM_REQUEST_SIZE];\r
688     mjson_snprintf(requestBuffer, KEYS_CLAIM_REQUEST_SIZE,\r
689     "{"\r
690       "\"one_time_keys\":{"\r
691         "\"%s\":{"\r
692           "\"%s\":\"signed_curve25519\""\r
693         "}"\r
694       "},"\r
695       "\"timeout\":10000"\r
696     "}",\r
697     userId,\r
698     deviceId);\r
699 \r
700     STATIC char responseBuffer[KEYS_CLAIM_RESPONSE_SIZE];\r
701     MatrixHttpPost(client->hc,\r
702         KEYS_CLAIM_URL,\r
703         requestBuffer,\r
704         responseBuffer, KEYS_CLAIM_RESPONSE_SIZE,\r
705         true);\r
706     \r
707     STATIC char userIdEscaped[USER_ID_SIZE];\r
708     JsonEscape(userId, strlen(userId),\r
709         userIdEscaped, USER_ID_SIZE);\r
710     \r
711     STATIC char query[JSON_QUERY_SIZE];\r
712     snprintf(query, JSON_QUERY_SIZE,\r
713         "$.one_time_keys.%s.%s",\r
714         userIdEscaped,\r
715         deviceId);\r
716     \r
717     const char * keyObject;\r
718     int keyObjectSize;\r
719     mjson_find(responseBuffer, strlen(responseBuffer),\r
720         query,\r
721         &keyObject, &keyObjectSize);\r
722     \r
723     int koff, klen, voff, vlen, vtype;\r
724     mjson_next(keyObject, keyObjectSize, 0,\r
725         &koff, &klen, &voff, &vlen, &vtype);\r
726     \r
727     mjson_get_string(keyObject + voff, vlen,\r
728         "$.key", outOnetimeKey, outOnetimeKeyCap);\r
729     \r
730     // TODO:verify signature\r
731     \r
732     return true;\r
733 }\r
734 \r
735 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login\r
736 bool\r
737 MatrixClientLoginPassword(\r
738     MatrixClient * client,\r
739     const char * username,\r
740     const char * password,\r
741     const char * displayName)\r
742 {\r
743     STATIC char requestBuffer[LOGIN_REQUEST_SIZE];\r
744 \r
745     mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,\r
746         "{"\r
747             "\"type\":\"m.login.password\","\r
748             "\"identifier\":{"\r
749                 "\"type\":\"m.id.user\","\r
750                 "\"user\":\"%s\""\r
751             "},"\r
752             "\"password\":\"%s\","\r
753             "\"initial_device_display_name\":\"%s\""\r
754         "}",\r
755         username,\r
756         password,\r
757         displayName);\r
758     \r
759     STATIC char responseBuffer[LOGIN_RESPONSE_SIZE];\r
760     bool result =\r
761         MatrixHttpPost(client->hc,\r
762             LOGIN_URL,\r
763             requestBuffer,\r
764             responseBuffer, LOGIN_RESPONSE_SIZE,\r
765             false);\r
766     \r
767     if (!result)\r
768         return false;\r
769     \r
770     int responseLen = strlen(responseBuffer);\r
771 \r
772     mjson_get_string(responseBuffer, responseLen,\r
773         "$.access_token",\r
774         client->accessToken, ACCESS_TOKEN_SIZE);\r
775     mjson_get_string(responseBuffer, responseLen,\r
776         "$.device_id",\r
777         client->deviceId, DEVICE_ID_SIZE);\r
778     mjson_get_string(responseBuffer, responseLen,\r
779         "$.expires_in_ms",\r
780         client->expireMs, EXPIRE_MS_SIZE);\r
781     mjson_get_string(responseBuffer, responseLen,\r
782         "$.refresh_token",\r
783         client->refreshToken, REFRESH_TOKEN_SIZE);\r
784         \r
785     MatrixHttpSetAccessToken(client->hc, client->accessToken);\r
786 \r
787     return true;\r
788 }\r
789 \r
790 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid\r
791 bool\r
792 MatrixClientSendEvent(\r
793     MatrixClient * client,\r
794     const char * roomId,\r
795     const char * msgType,\r
796     const char * msgBody)\r
797 {    \r
798     STATIC char requestUrl[MAX_URL_LEN];\r
799     sprintf(requestUrl,\r
800         ROOM_SEND_URL, roomId, msgType, (int)time(NULL));\r
801 \r
802     STATIC char responseBuffer[ROOM_SEND_RESPONSE_SIZE];\r
803     bool result =\r
804         MatrixHttpPut(client->hc,\r
805             requestUrl,\r
806             msgBody,\r
807             responseBuffer, ROOM_SEND_RESPONSE_SIZE,\r
808             true);\r
809     \r
810     return result;\r
811 }\r
812 \r
813 // https://spec.matrix.org/v1.6/client-server-api/#mroomencrypted\r
814 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event\r
815 bool\r
816 MatrixClientSendEventEncrypted(\r
817     MatrixClient * client,\r
818     const char * roomId,\r
819     const char * msgType,\r
820     const char * msgBody)\r
821 {\r
822     // event json\r
823     STATIC char requestBuffer[ROOM_SEND_REQUEST_SIZE];\r
824     sprintf(requestBuffer,\r
825         "{"\r
826         "\"type\":\"%s\","\r
827         "\"content\":%s,"\r
828         "\"room_id\":\"%s\""\r
829         "}",\r
830         msgType,\r
831         msgBody,\r
832         roomId);\r
833 \r
834     // get megolm session\r
835     MatrixMegolmOutSession * outSession;\r
836     if (! MatrixClientGetMegolmOutSession(client, roomId, &outSession))\r
837         MatrixClientNewMegolmOutSession(client, roomId, &outSession);\r
838         \r
839     // encrypt\r
840     MatrixMegolmOutSessionEncrypt(outSession,\r
841         requestBuffer,\r
842         g_EncryptedRequestBuffer, ENCRYPTED_REQUEST_SIZE);\r
843 \r
844     char thisDeviceKey[DEVICE_KEY_SIZE];\r
845     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
846     \r
847 \r
848     // encrypted event json\r
849     const char * senderKey = thisDeviceKey;\r
850     const char * sessionId = outSession->id;\r
851     const char * deviceId = client->deviceId;\r
852 \r
853     snprintf(g_EncryptedEventBuffer, ENCRYPTED_EVENT_SIZE,\r
854         "{"\r
855         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
856         "\"ciphertext\":\"%s\","\r
857         "\"device_id\":\"%s\","\r
858         "\"sender_key\":\"%s\","\r
859         "\"session_id\":\"%s\""\r
860         "}",\r
861         g_EncryptedRequestBuffer,\r
862         deviceId,\r
863         senderKey,\r
864         sessionId);\r
865 \r
866     // send\r
867     return MatrixClientSendEvent(client,\r
868         roomId,\r
869         "m.room.encrypted",\r
870         g_EncryptedEventBuffer);\r
871 }\r
872 \r
873 void\r
874 MatrixClientHandleEvent(\r
875     MatrixClient * client,\r
876     const char * event, int eventLen\r
877 ) {\r
878     STATIC char eventType[128];\r
879     memset(eventType, 0, sizeof(eventType));\r
880     mjson_get_string(event, eventLen, "$.type", eventType, 128);\r
881 \r
882     static char transactionId[64];\r
883     static char verifyFromDeviceId[DEVICE_ID_SIZE];\r
884     static OlmSAS * olmSas = NULL;\r
885 \r
886     if (strcmp(eventType, "m.key.verification.request") == 0) {\r
887         memset(transactionId, 0, 64);\r
888         if (olmSas != NULL)\r
889             free(olmSas);\r
890         \r
891         mjson_get_string(event, eventLen, "$.content.transaction_id", transactionId, 64);\r
892         mjson_get_string(event, eventLen, "$.content.from_device", verifyFromDeviceId, DEVICE_ID_SIZE);\r
893         \r
894         char verificationReadyBuffer[2048];\r
895         snprintf(verificationReadyBuffer, 2048,\r
896             "{"\r
897             "\"from_device\":\"%s\","\r
898             "\"methods\":[\"m.sas.v1\"],"\r
899             "\"transaction_id\":\"%s\""\r
900             "}",\r
901             client->deviceId,\r
902             transactionId);\r
903         \r
904         MatrixClientSendToDevice(client,\r
905             client->userId,\r
906             verifyFromDeviceId,\r
907             verificationReadyBuffer,\r
908             "m.key.verification.ready");\r
909     }\r
910     else if (strcmp(eventType, "m.key.verification.start") == 0) {\r
911         olmSas = olm_sas(malloc(olm_sas_size()));\r
912         void * sasRandomBytes = malloc(olm_create_sas_random_length(olmSas));\r
913         olm_create_sas(olmSas,\r
914             sasRandomBytes,\r
915             olm_create_sas_random_length(olmSas));\r
916         \r
917         OlmUtility * olmUtil = olm_utility(malloc(olm_utility_size()));\r
918         \r
919         STATIC char publicKey[64];\r
920         STATIC char keyStartJsonCanonical[512];\r
921         STATIC char concat[512+64];\r
922         STATIC char commitment[1024];\r
923         olm_sas_get_pubkey(olmSas,\r
924             publicKey,\r
925             64);\r
926         printf("public key: %.*s\n", olm_sas_pubkey_length(olmSas), publicKey);\r
927 \r
928         const char * keyStartJson;\r
929         int keyStartJsonLen;\r
930         mjson_find(event, eventLen, "$.content", &keyStartJson, &keyStartJsonLen);\r
931         JsonCanonicalize(keyStartJson, keyStartJsonLen, keyStartJsonCanonical, 512);\r
932 \r
933         printf("json:\n%.*s\ncanonical json:\n%s\n", keyStartJsonLen, keyStartJson, keyStartJsonCanonical);\r
934 \r
935         int concatLen =\r
936             snprintf(concat, 512+64, "%.*s%s", olm_sas_pubkey_length(olmSas), publicKey, keyStartJsonCanonical);\r
937 \r
938         int commitmentLen =\r
939             olm_sha256(olmUtil, concat, concatLen, commitment, 1024);\r
940         \r
941         STATIC char verificationAcceptBuffer[512];\r
942         snprintf(verificationAcceptBuffer, 512,\r
943             "{"\r
944             "\"commitment\":\"%.*s\","\r
945             "\"hash\":\"sha256\","\r
946             "\"key_agreement_protocol\":\"curve25519\","\r
947             "\"message_authentication_code\":\"hkdf-hmac-sha256.v2\","\r
948             "\"method\":\"m.sas.v1\","\r
949             "\"short_authentication_string\":[\"decimal\"],"\r
950             "\"transaction_id\":\"%s\""\r
951             "}",\r
952             commitmentLen, commitment,\r
953             transactionId);\r
954         \r
955         MatrixClientSendToDevice(client,\r
956             client->userId,\r
957             verifyFromDeviceId,\r
958             verificationAcceptBuffer,\r
959             "m.key.verification.accept");\r
960     }\r
961     else if (strcmp(eventType, "m.key.verification.key") == 0) {\r
962         STATIC char publicKey[128];\r
963         olm_sas_get_pubkey(olmSas,\r
964             publicKey,\r
965             128);\r
966 \r
967         STATIC char theirPublicKey[128];\r
968         int theirPublicKeyLen =\r
969             mjson_get_string(event, eventLen, "$.content.key", theirPublicKey, 128);\r
970         \r
971         printf("event: %.*s\n", eventLen, event);\r
972         printf("theirPublicKey: %.*s\n", theirPublicKeyLen, theirPublicKey);\r
973         printf("publicKey: %.*s\n", olm_sas_pubkey_length(olmSas), publicKey);\r
974 \r
975         olm_sas_set_their_key(olmSas, theirPublicKey, theirPublicKeyLen);\r
976         \r
977         STATIC char verificationKeyBuffer[256];\r
978         snprintf(verificationKeyBuffer, 256,\r
979             "{"\r
980             "\"key\":\"%.*s\","\r
981             "\"transaction_id\":\"%s\""\r
982             "}",\r
983             olm_sas_pubkey_length(olmSas), publicKey,\r
984             transactionId);\r
985         \r
986         MatrixClientSendToDevice(client,\r
987             client->userId,\r
988             verifyFromDeviceId,\r
989             verificationKeyBuffer,\r
990             "m.key.verification.key");\r
991         \r
992         // sas\r
993         STATIC char hkdfInfo[1024];\r
994         int hkdfInfoLen =\r
995             snprintf(hkdfInfo, 1024,\r
996                 "MATRIX_KEY_VERIFICATION_SAS%s%s%s%s%s",\r
997                 client->userId,\r
998                 verifyFromDeviceId,\r
999                 client->userId,\r
1000                 client->deviceId,\r
1001                 transactionId);\r
1002 \r
1003         unsigned char sasBytes[5];\r
1004         olm_sas_generate_bytes(olmSas,\r
1005             hkdfInfo, hkdfInfoLen,\r
1006             sasBytes, 5);\r
1007         int b0 = sasBytes[0];\r
1008         int b1 = sasBytes[1];\r
1009         int b2 = sasBytes[2];\r
1010         int b3 = sasBytes[3];\r
1011         int b4 = sasBytes[4];\r
1012         \r
1013         printf("%d %d %d %d %d\n", b0, b1, b2, b3, b4);\r
1014 \r
1015         // https://spec.matrix.org/v1.7/client-server-api/#sas-method-decimal\r
1016         printf("%d | %d | %d\n",\r
1017             (b0 << 5 | b1 >> 3) + 1000,\r
1018             ((b1 & 0x7) << 10 | b2 << 2 | b3 >> 6) + 1000,\r
1019             ((b3 & 0x3F) << 7 | b4 >> 1) + 1000);\r
1020         printf("%d | %d | %d\n",\r
1021             ((b0 << 5) | (b1 >> 3)) + 1000,\r
1022             (((b1 & 0x7) << 10) | (b2 << 2) | (b3 >> 6)) + 1000,\r
1023             (((b3 & 0x3F) << 7) | (b4 >> 1)) + 1000);\r
1024     }\r
1025     else if (strcmp(eventType, "m.key.verification.mac") == 0) {        \r
1026         // mac\r
1027         STATIC char masterKey[123];\r
1028         MatrixClientRequestMasterKey(client, masterKey, 123);\r
1029 \r
1030         STATIC char keyList[256];\r
1031         STATIC char keyListMac[256];\r
1032         STATIC char key1Id[128];\r
1033         STATIC char key1[128];\r
1034         STATIC char key1Mac[128];\r
1035         STATIC char key2Id[128];\r
1036         STATIC char key2[128];\r
1037         STATIC char key2Mac[128];\r
1038 \r
1039         if (strcmp(masterKey, client->deviceId) < 0) {\r
1040             snprintf(key1Id, 1024, "ed25519:%s", masterKey);\r
1041             strcpy(key1, masterKey);\r
1042             snprintf(key2Id, 1024, "ed25519:%s", client->deviceId);\r
1043             MatrixOlmAccountGetSigningKey(&client->olmAccount, key2, 1024);\r
1044         }\r
1045         else {\r
1046             snprintf(key1Id, 1024, "ed25519:%s", client->deviceId);\r
1047             MatrixOlmAccountGetSigningKey(&client->olmAccount, key1, 1024);\r
1048             snprintf(key2Id, 1024, "ed25519:%s", masterKey);\r
1049             strcpy(key2, masterKey);\r
1050         }\r
1051 \r
1052         snprintf(keyList, 1024,\r
1053             "%s,%s", key1Id, key2Id);\r
1054         \r
1055         STATIC char macInfo[1024];\r
1056         int macInfoLen;\r
1057         {\r
1058             macInfoLen =\r
1059                 snprintf(macInfo, 1024,\r
1060                     "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",\r
1061                     client->userId,\r
1062                     client->deviceId,\r
1063                     client->userId,\r
1064                     verifyFromDeviceId,\r
1065                     transactionId,\r
1066                     "KEY_IDS");\r
1067             olm_sas_calculate_mac_fixed_base64(olmSas, keyList, strlen(keyList), macInfo, macInfoLen, keyListMac, 1024);\r
1068         }\r
1069         {\r
1070             macInfoLen =\r
1071                 snprintf(macInfo, 1024,\r
1072                     "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",\r
1073                     client->userId,\r
1074                     client->deviceId,\r
1075                     client->userId,\r
1076                     verifyFromDeviceId,\r
1077                     transactionId,\r
1078                     key1Id);\r
1079             olm_sas_calculate_mac_fixed_base64(olmSas, key1, strlen(key1), macInfo, macInfoLen, key1Mac, 1024);\r
1080         }\r
1081         {\r
1082             macInfoLen =\r
1083                 snprintf(macInfo, 1024,\r
1084                     "MATRIX_KEY_VERIFICATION_MAC%s%s%s%s%s%s",\r
1085                     client->userId,\r
1086                     client->deviceId,\r
1087                     client->userId,\r
1088                     verifyFromDeviceId,\r
1089                     transactionId,\r
1090                     key2Id);\r
1091             olm_sas_calculate_mac_fixed_base64(olmSas, key2, strlen(key2), macInfo, macInfoLen, key2Mac, 1024);\r
1092         }\r
1093 \r
1094         STATIC char verificationMacBuffer[1024];\r
1095         snprintf(verificationMacBuffer, 1024,\r
1096             "{"\r
1097             "\"keys\":\"%s\","\r
1098             "\"mac\":{"\r
1099             "\"%s\":\"%s\","\r
1100             "\"%s\":\"%s\""\r
1101             "},"\r
1102             "\"transaction_id\":\"%s\""\r
1103             "}",\r
1104             keyListMac,\r
1105             key1Id,\r
1106             key1Mac,\r
1107             key2Id,\r
1108             key2Mac,\r
1109             transactionId);\r
1110         \r
1111         MatrixClientSendToDevice(client,\r
1112             client->userId,\r
1113             verifyFromDeviceId,\r
1114             verificationMacBuffer,\r
1115             "m.key.verification.mac");\r
1116 \r
1117         STATIC char verificationDoneBuffer[128];\r
1118         snprintf(verificationDoneBuffer, 128,\r
1119             "{"\r
1120             "\"transaction_id\":\"%s\""\r
1121             "}",\r
1122             transactionId);\r
1123         \r
1124         MatrixClientSendToDevice(client,\r
1125             client->userId,\r
1126             verifyFromDeviceId,\r
1127             verificationDoneBuffer,\r
1128             "m.key.verification.done");\r
1129         \r
1130         free(olmSas);\r
1131         client->verified = true;\r
1132     }\r
1133     else if (strcmp(eventType, "m.room.encrypted") == 0) {\r
1134         STATIC char algorithm[128];\r
1135         mjson_get_string(event, eventLen, "$.content.algorithm", algorithm, 128);\r
1136 \r
1137         if (strcmp(algorithm, "m.olm.v1.curve25519-aes-sha2") == 0) {\r
1138             STATIC char thisDeviceKey[DEVICE_KEY_SIZE];\r
1139             MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
1140 \r
1141             STATIC char jp[128];\r
1142             snprintf(jp, 128, "$.content.ciphertext.%s.type", thisDeviceKey);\r
1143 \r
1144             double messageType;\r
1145             mjson_get_number(event, eventLen, jp, &messageType);\r
1146             int messageTypeInt = (int)messageType;\r
1147 \r
1148             snprintf(jp, 128, "$.content.ciphertext.%s.body", thisDeviceKey);\r
1149 \r
1150             mjson_get_string(event, eventLen, jp, g_EncryptedEventBuffer, 2048);\r
1151 \r
1152             MatrixOlmSession * olmSession;\r
1153             \r
1154             if (! MatrixClientGetOlmSession(client, client->userId, verifyFromDeviceId, &olmSession))\r
1155             {\r
1156                 if (messageTypeInt == 0) {\r
1157                     MatrixClientNewOlmSessionIn(client,\r
1158                         client->userId,\r
1159                         verifyFromDeviceId,\r
1160                         g_EncryptedEventBuffer,\r
1161                         &olmSession);\r
1162                 }\r
1163                 else {\r
1164                     MatrixClientNewOlmSessionOut(client,\r
1165                         client->userId,\r
1166                         verifyFromDeviceId,\r
1167                         &olmSession);\r
1168                 }\r
1169             }\r
1170             \r
1171             STATIC char decrypted[2048];\r
1172             MatrixOlmSessionDecrypt(olmSession,\r
1173                 messageTypeInt, g_EncryptedEventBuffer, decrypted, 2048);\r
1174             \r
1175             MatrixClientHandleEvent(client, decrypted, strlen(decrypted));\r
1176         }\r
1177     }\r
1178     else if (strcmp(eventType, "m.room_key") == 0 ||\r
1179              strcmp(eventType, "m.forwarded_room_key") == 0) {\r
1180         STATIC char roomId[128];\r
1181         STATIC char sessionId[128];\r
1182         STATIC char sessionKey[1024];\r
1183         mjson_get_string(event, eventLen, "$.content.room_id", roomId, 128);\r
1184         mjson_get_string(event, eventLen, "$.content.session_id", sessionId, 128);\r
1185         mjson_get_string(event, eventLen, "$.content.session_key", sessionKey, 1024);\r
1186         \r
1187         printf("sessionId: %s\n", sessionId);\r
1188         printf("sessionKey: %s\n", sessionKey);\r
1189 \r
1190         MatrixMegolmInSession * megolmInSession;\r
1191         MatrixClientNewMegolmInSession(client, roomId, sessionId, sessionKey, &megolmInSession);\r
1192     }\r
1193 }\r
1194 \r
1195 void\r
1196 MatrixClientHandleRoomEvent(\r
1197     MatrixClient * client,\r
1198     const char * room, int roomLen,\r
1199     const char * event, int eventLen)\r
1200 {\r
1201     STATIC char eventType[128];\r
1202     memset(eventType, 0, sizeof(eventType));\r
1203     mjson_get_string(event, eventLen, "$.type", eventType, 128);\r
1204 \r
1205     if (strcmp(eventType, "m.room.encrypted") == 0) {\r
1206         STATIC char algorithm[128];\r
1207         mjson_get_string(event, eventLen, "$.content.algorithm", algorithm, 128);\r
1208 \r
1209         if (strcmp(algorithm, "m.megolm.v1.aes-sha2") == 0) {\r
1210             STATIC char sessionId[128];\r
1211             int sessionIdLen =\r
1212                 mjson_get_string(event, eventLen, "$.content.session_id", sessionId, 128);\r
1213 \r
1214             bool res;\r
1215 \r
1216             MatrixMegolmInSession * megolmInSession;\r
1217             res = MatrixClientGetMegolmInSession(client,\r
1218                 room, roomLen,\r
1219                 sessionId, sessionIdLen,\r
1220                 &megolmInSession);\r
1221 \r
1222             if (res) {\r
1223                 mjson_get_string(event, eventLen, "$.content.ciphertext", g_EncryptedEventBuffer, 2048);\r
1224 \r
1225                 STATIC char decrypted[2048];\r
1226                 MatrixMegolmInSessionDecrypt(megolmInSession, g_EncryptedEventBuffer, strlen(g_EncryptedEventBuffer), decrypted, 2048);\r
1227 \r
1228                 MatrixClientHandleEvent(client, decrypted, strlen(decrypted));\r
1229             }\r
1230             else {\r
1231                 printf("megolm session not known\n");\r
1232             }\r
1233         }\r
1234     }\r
1235     MatrixClientHandleEvent(client, event, eventLen);\r
1236 }\r
1237 \r
1238 void\r
1239 MatrixClientHandleSync(\r
1240     MatrixClient * client,\r
1241     char * syncBuffer, int syncBufferLen,\r
1242     char * nextBatch, int nextBatchCap)\r
1243 {    \r
1244     int res;\r
1245 \r
1246     const char * s = syncBuffer;\r
1247     int slen = syncBufferLen;\r
1248 \r
1249     mjson_get_string(s, slen, "$.next_batch", nextBatch, nextBatchCap);\r
1250 \r
1251     // to_device\r
1252 \r
1253     const char * events;\r
1254     int eventsLen;\r
1255     res =\r
1256         mjson_find(s, slen, "$.to_device.events", &events, &eventsLen);\r
1257     \r
1258     if (res != MJSON_TOK_INVALID) {\r
1259         {\r
1260         int koff, klen, voff, vlen, vtype, off = 0;\r
1261         for (off = 0; (off = mjson_next(events, eventsLen, off, &koff, &klen,\r
1262                                         &voff, &vlen, &vtype)) != 0; ) {\r
1263             const char * v = events + voff;\r
1264 \r
1265             MatrixClientHandleEvent(client, v, vlen);\r
1266         }\r
1267         }\r
1268     }\r
1269 \r
1270     // rooms\r
1271     \r
1272     const char * rooms;\r
1273     int roomsLen;\r
1274     res =\r
1275         mjson_find(s, slen, "$.rooms.join", &rooms, &roomsLen);\r
1276     \r
1277     if (res != MJSON_TOK_INVALID) {\r
1278         {\r
1279         int koff, klen, voff, vlen, vtype, off = 0;\r
1280         for (off = 0; (off = mjson_next(rooms, roomsLen, off, &koff, &klen,\r
1281                                         &voff, &vlen, &vtype)) != 0; ) {\r
1282             const char * k = rooms + koff;\r
1283             const char * v = rooms + voff;\r
1284 \r
1285             const char * events;\r
1286             int eventsLen;\r
1287             res =\r
1288                 mjson_find(v, vlen, "$.timeline.events", &events, &eventsLen);\r
1289             \r
1290             if (res != MJSON_TOK_INVALID) {\r
1291                 {\r
1292                 int koff2, klen2, voff2, vlen2, vtype2, off2 = 0;\r
1293                 for (off2 = 0; (off2 = mjson_next(events, eventsLen, off2, &koff2, &klen2,\r
1294                                                 &voff2, &vlen2, &vtype2)) != 0; ) {\r
1295                     const char * v2 = events + voff2;\r
1296 \r
1297                     MatrixClientHandleRoomEvent(client,\r
1298                         k+1, klen-2,\r
1299                         v2, vlen2);\r
1300                 }\r
1301                 }\r
1302             }\r
1303         }\r
1304         }\r
1305     }\r
1306 }\r
1307 \r
1308 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync\r
1309 bool\r
1310 MatrixClientSync(\r
1311     MatrixClient * client,\r
1312     char * outSyncBuffer, int outSyncCap,\r
1313     char * nextBatch, int nextBatchCap)\r
1314 {\r
1315     // filter={\"event_fields\":[\"to_device\"]}\r
1316     STATIC char url[MAX_URL_LEN];\r
1317     snprintf(url, MAX_URL_LEN,\r
1318         "/_matrix/client/v3/sync?timeout=%d" "%s" "%s",\r
1319         SYNC_TIMEOUT,\r
1320         "",\r
1321         // "&filter={\"event_fields\":[\"to_device\"]}",\r
1322         strlen(nextBatch) > 0 ? "&since=" : "");\r
1323     \r
1324     int index = strlen(url);\r
1325 \r
1326     for (size_t i = 0; i < strlen(nextBatch); i++) {\r
1327         char c = nextBatch[i];\r
1328 \r
1329         if (c == '~') {\r
1330             url[index++] = '%';\r
1331             url[index++] = '7';\r
1332             url[index++] = 'E';\r
1333         }\r
1334         else {\r
1335             url[index++] = c;\r
1336         }\r
1337     }\r
1338     url[index] = '\0';\r
1339 \r
1340     bool result =\r
1341         MatrixHttpGet(client->hc,\r
1342             url,\r
1343             outSyncBuffer, outSyncCap,\r
1344             true);\r
1345     \r
1346     MatrixClientHandleSync(client,\r
1347         outSyncBuffer, strlen(outSyncBuffer),\r
1348         nextBatch, nextBatchCap);\r
1349     \r
1350     return result;\r
1351 }\r
1352 \r
1353 // https://spec.matrix.org/v1.7/client-server-api/#get_matrixclientv3roomsroomideventeventid\r
1354 bool\r
1355 MatrixClientGetRoomEvent(\r
1356     MatrixClient * client,\r
1357     const char * roomId,\r
1358     const char * eventId,\r
1359     char * outEvent, int outEventCap)\r
1360 {\r
1361     STATIC char url[MAX_URL_LEN];\r
1362     snprintf(url, MAX_URL_LEN,\r
1363         "/_matrix/client/v3/rooms/%s/event/%s",\r
1364             roomId,\r
1365             eventId);\r
1366 \r
1367     return\r
1368         MatrixHttpGet(client->hc,\r
1369             url,\r
1370             outEvent, outEventCap,\r
1371             true);\r
1372 }\r
1373 \r
1374 bool\r
1375 MatrixClientShareMegolmOutSession(\r
1376     MatrixClient * client,\r
1377     const char * userId,\r
1378     const char * deviceId,\r
1379     MatrixMegolmOutSession * session)\r
1380 {\r
1381     // generate room key event\r
1382     STATIC char eventBuffer[KEY_SHARE_EVENT_LEN];\r
1383     sprintf(eventBuffer,\r
1384         "{"\r
1385             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
1386             "\"room_id\":\"%s\","\r
1387             "\"session_id\":\"%s\","\r
1388             "\"session_key\":\"%s\""\r
1389         "}",\r
1390         session->roomId,\r
1391         session->id,\r
1392         session->key\r
1393     );\r
1394 \r
1395     // send\r
1396     MatrixClientSendToDeviceEncrypted(client,\r
1397         userId,\r
1398         deviceId,\r
1399         eventBuffer,\r
1400         "m.room_key");\r
1401 \r
1402     return true;\r
1403 }\r
1404 \r
1405 bool\r
1406 MatrixClientShareMegolmOutSessionTest(\r
1407     MatrixClient * client,\r
1408     const char * userId,\r
1409     const char * deviceId,\r
1410     MatrixMegolmOutSession * session)\r
1411 {\r
1412     // generate room key event\r
1413     char eventBuffer[KEY_SHARE_EVENT_LEN];\r
1414     sprintf(eventBuffer,\r
1415         "{"\r
1416             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
1417             "\"room_id\":\"%s\","\r
1418             "\"session_id\":\"%s\","\r
1419             "\"session_key\":\"%s\""\r
1420         "}",\r
1421         session->roomId,\r
1422         session->id,\r
1423         session->key\r
1424     );\r
1425 \r
1426     // send\r
1427     MatrixClientSendToDevice(client,\r
1428         userId,\r
1429         deviceId,\r
1430         eventBuffer,\r
1431         "m.room_key");\r
1432 \r
1433     return true;\r
1434 }\r
1435 \r
1436 bool\r
1437 MatrixClientGetMegolmOutSession(\r
1438     MatrixClient * client,\r
1439     const char * roomId,\r
1440     MatrixMegolmOutSession ** outSession)\r
1441 {\r
1442     for (int i = 0; i < client->numMegolmOutSessions; i++)\r
1443     {\r
1444         if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)\r
1445         {\r
1446             *outSession = &client->megolmOutSessions[i];\r
1447             return true;\r
1448         }\r
1449     }\r
1450 \r
1451     return false;\r
1452 }\r
1453 \r
1454 bool\r
1455 MatrixClientNewMegolmOutSession(\r
1456     MatrixClient * client,\r
1457     const char * roomId,\r
1458     MatrixMegolmOutSession ** outSession)\r
1459 {\r
1460     if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)\r
1461     {\r
1462         MatrixMegolmOutSession * result =\r
1463             &client->megolmOutSessions[client->numMegolmOutSessions];\r
1464         \r
1465         MatrixMegolmOutSessionInit(result,\r
1466             roomId);\r
1467 \r
1468         *outSession = result;\r
1469 \r
1470         client->numMegolmOutSessions++;\r
1471 \r
1472         return true;\r
1473     }\r
1474 \r
1475     return false;\r
1476 }\r
1477 \r
1478 bool\r
1479 MatrixClientGetMegolmInSession(\r
1480     MatrixClient * client,\r
1481     const char * roomId, int roomIdLen,\r
1482     const char * sessionId, int sessionIdLen,\r
1483     MatrixMegolmInSession ** outSession)\r
1484 {\r
1485     for (int i = 0; i < client->numMegolmInSessions; i++)\r
1486     {\r
1487         if (strncmp(client->megolmInSessions[i].roomId, roomId, roomIdLen) == 0 &&\r
1488             strncmp(client->megolmInSessions[i].id, sessionId, sessionIdLen) == 0)\r
1489         {\r
1490             *outSession = &client->megolmInSessions[i];\r
1491             return true;\r
1492         }\r
1493     }\r
1494 \r
1495     return false;\r
1496 }\r
1497 \r
1498 bool\r
1499 MatrixClientNewMegolmInSession(\r
1500     MatrixClient * client,\r
1501     const char * roomId,\r
1502     const char * sessionId,\r
1503     const char * sessionKey,\r
1504     MatrixMegolmInSession ** outSession)\r
1505 {\r
1506     if (client->numMegolmInSessions < NUM_MEGOLM_SESSIONS)\r
1507     {\r
1508         MatrixMegolmInSession * result =\r
1509             &client->megolmInSessions[client->numMegolmInSessions];\r
1510         \r
1511         MatrixMegolmInSessionInit(result,\r
1512             roomId,\r
1513             sessionId,\r
1514             sessionKey, strlen(sessionKey));\r
1515         \r
1516         *outSession = result;\r
1517 \r
1518         client->numMegolmInSessions++;\r
1519 \r
1520         return true;\r
1521     }\r
1522 \r
1523     return false;\r
1524 }\r
1525 \r
1526 bool\r
1527 MatrixClientRequestMegolmInSession(\r
1528     MatrixClient * client,\r
1529     const char * roomId,\r
1530     const char * sessionId,\r
1531     const char * senderKey,\r
1532     const char * userId,\r
1533     const char * deviceId)\r
1534 {\r
1535     // TODO: cancel requests\r
1536     MatrixClientSendDummy(client, userId, deviceId);\r
1537 \r
1538     STATIC char event[ROOMKEY_REQUEST_SIZE];\r
1539     snprintf(event, ROOMKEY_REQUEST_SIZE,\r
1540         "{"\r
1541             "\"action\":\"request\","\r
1542             "\"body\":{"\r
1543                 "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
1544                 "\"room_id\":\"%s\","\r
1545                 "\"sender_key\":\"%s\","\r
1546                 "\"session_id\":\"%s\""\r
1547             "},"\r
1548             "\"request_id\":\"%lld\","\r
1549             "\"requesting_device_id\":\"%s\""\r
1550         "}",\r
1551         roomId,\r
1552         senderKey,\r
1553         sessionId,\r
1554         time(NULL),\r
1555         client->deviceId);\r
1556 \r
1557     \r
1558     MatrixClientSendToDevice(client,\r
1559         userId,\r
1560         deviceId,\r
1561         event,\r
1562         "m.room_key_request");\r
1563 \r
1564     return true;\r
1565 }\r
1566 \r
1567 bool\r
1568 MatrixClientGetOlmSession(\r
1569     MatrixClient * client,\r
1570     const char * userId,\r
1571     const char * deviceId,\r
1572     MatrixOlmSession ** outSession)\r
1573 {\r
1574     (void)userId; //unused for now\r
1575 \r
1576     for (int i = 0; i < client->numOlmSessions; i++)\r
1577     {\r
1578         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
1579         {\r
1580             *outSession = &client->olmSessions[i];\r
1581             return true;\r
1582         }\r
1583     }\r
1584 \r
1585     return false;\r
1586 }\r
1587 \r
1588 bool\r
1589 MatrixClientNewOlmSessionIn(\r
1590     MatrixClient * client,\r
1591     const char * userId,\r
1592     const char * deviceId,\r
1593     const char * encrypted,\r
1594     MatrixOlmSession ** outSession)\r
1595 {\r
1596     (void)userId; //unused for now\r
1597     \r
1598     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
1599     {\r
1600         STATIC char deviceKey[DEVICE_KEY_SIZE];\r
1601         MatrixClientRequestDeviceKey(client,\r
1602             deviceId,\r
1603             deviceKey, DEVICE_KEY_SIZE);\r
1604 \r
1605         MatrixOlmSessionFrom(\r
1606             &client->olmSessions[client->numOlmSessions],\r
1607             client->olmAccount.account,\r
1608             deviceId,\r
1609             deviceKey,\r
1610             encrypted);\r
1611 \r
1612         *outSession = &client->olmSessions[client->numOlmSessions];\r
1613         \r
1614         client->numOlmSessions++;\r
1615 \r
1616         return true;\r
1617     }\r
1618 \r
1619     return false;\r
1620 }\r
1621 \r
1622 bool\r
1623 MatrixClientNewOlmSessionOut(\r
1624     MatrixClient * client,\r
1625     const char * userId,\r
1626     const char * deviceId,\r
1627     MatrixOlmSession ** outSession)\r
1628 {\r
1629     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
1630     {\r
1631         STATIC char deviceKey[DEVICE_KEY_SIZE];\r
1632         MatrixClientRequestDeviceKey(client,\r
1633             deviceId,\r
1634             deviceKey, DEVICE_KEY_SIZE);\r
1635 \r
1636         char onetimeKey[ONETIME_KEY_SIZE];\r
1637         MatrixClientClaimOnetimeKey(client,\r
1638             userId,\r
1639             deviceId,\r
1640             onetimeKey, ONETIME_KEY_SIZE);\r
1641 \r
1642         MatrixOlmSessionTo(\r
1643             &client->olmSessions[client->numOlmSessions],\r
1644             client->olmAccount.account,\r
1645             deviceId,\r
1646             deviceKey,\r
1647             onetimeKey);\r
1648 \r
1649         *outSession = &client->olmSessions[client->numOlmSessions];\r
1650         \r
1651         client->numOlmSessions++;\r
1652 \r
1653         return true;\r
1654     }\r
1655 \r
1656     return false;\r
1657 }\r
1658 \r
1659 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid\r
1660 bool\r
1661 MatrixClientSendToDevice(\r
1662     MatrixClient * client,\r
1663     const char * userId,\r
1664     const char * deviceId,\r
1665     const char * message,\r
1666     const char * msgType)\r
1667 {\r
1668     STATIC char requestUrl[MAX_URL_LEN];\r
1669     sprintf(requestUrl,\r
1670         TODEVICE_URL, msgType, (int)time(NULL));\r
1671 \r
1672     snprintf(g_TodeviceEventBuffer, TODEVICE_EVENT_SIZE,\r
1673         "{"\r
1674             "\"messages\":{"\r
1675                 "\"%s\":{"\r
1676                     "\"%s\":%s"\r
1677                 "}"\r
1678             "}"\r
1679         "}",\r
1680         userId,\r
1681         deviceId,\r
1682         message);\r
1683 \r
1684     STATIC char responseBuffer[ROOM_SEND_RESPONSE_SIZE];\r
1685     bool result =\r
1686         MatrixHttpPut(client->hc,\r
1687             requestUrl,\r
1688             g_TodeviceEventBuffer,\r
1689             responseBuffer, ROOM_SEND_RESPONSE_SIZE,\r
1690             true);\r
1691     \r
1692     printf("%s\n", responseBuffer);\r
1693     \r
1694     return result;\r
1695 }\r
1696 \r
1697 bool\r
1698 MatrixClientSendToDeviceEncrypted(\r
1699     MatrixClient * client,\r
1700     const char * userId,\r
1701     const char * deviceId,\r
1702     const char * message,\r
1703     const char * msgType)\r
1704 {\r
1705     // get olm session\r
1706     MatrixOlmSession * olmSession;\r
1707     if (! MatrixClientGetOlmSession(client, userId, deviceId, &olmSession))\r
1708         MatrixClientNewOlmSessionOut(client, userId, deviceId, &olmSession);\r
1709 \r
1710     // create event json\r
1711     char targetDeviceKey[DEVICE_KEY_SIZE];\r
1712     MatrixClientRequestDeviceKey(client, deviceId, targetDeviceKey, DEVICE_KEY_SIZE);\r
1713     char targetSigningKey[SIGNING_KEY_SIZE];\r
1714     MatrixClientRequestSigningKey(client, deviceId, targetSigningKey, SIGNING_KEY_SIZE);\r
1715     \r
1716     char thisSigningKey[DEVICE_KEY_SIZE];\r
1717     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);\r
1718 \r
1719     snprintf(g_TodeviceEventBuffer, TODEVICE_EVENT_SIZE,\r
1720         "{"\r
1721         "\"type\":\"%s\","\r
1722         "\"content\":%s,"\r
1723         "\"sender\":\"%s\","\r
1724         "\"recipient\":\"%s\","\r
1725         "\"recipient_keys\":{"\r
1726         "\"ed25519\":\"%s\""\r
1727         "},"\r
1728         "\"keys\":{"\r
1729         "\"ed25519\":\"%s\""\r
1730         "}"\r
1731         "}",\r
1732         msgType,\r
1733         message,\r
1734         client->userId,\r
1735         userId, // recipient user id\r
1736         targetSigningKey, // recipient device key\r
1737         thisSigningKey);\r
1738 \r
1739     // encrypt\r
1740     MatrixOlmSessionEncrypt(olmSession,\r
1741         g_TodeviceEventBuffer,\r
1742         g_EncryptedRequestBuffer, ENCRYPTED_REQUEST_SIZE);\r
1743 \r
1744     char thisDeviceKey[DEVICE_KEY_SIZE];\r
1745     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
1746 \r
1747     snprintf(g_EncryptedEventBuffer, ENCRYPTED_EVENT_SIZE,\r
1748         "{"\r
1749         "\"algorithm\":\"m.olm.v1.curve25519-aes-sha2\","\r
1750         "\"ciphertext\":{"\r
1751           "\"%s\":{"\r
1752             "\"body\":\"%s\","\r
1753             "\"type\":%d"\r
1754           "}"\r
1755         "},"\r
1756         "\"device_id\":\"%s\","\r
1757         "\"sender_key\":\"%s\""\r
1758         "}",\r
1759         targetDeviceKey,\r
1760         // olm_encrypt_message_length(olmSession->session, strlen(g_TodeviceEventBuffer)), g_EncryptedRequestBuffer,\r
1761         g_EncryptedRequestBuffer,\r
1762         olm_session_has_received_message(olmSession->session),\r
1763         client->deviceId,\r
1764         thisDeviceKey);\r
1765 \r
1766     // send\r
1767     return MatrixClientSendToDevice(\r
1768         client,\r
1769         userId,\r
1770         deviceId,\r
1771         g_EncryptedEventBuffer,\r
1772         "m.room.encrypted");\r
1773 }\r
1774 \r
1775 bool\r
1776 MatrixClientSendDummy(\r
1777     MatrixClient * client,\r
1778     const char * userId,\r
1779     const char * deviceId)\r
1780 {\r
1781     return MatrixClientSendToDeviceEncrypted(\r
1782         client,\r
1783         userId,\r
1784         deviceId,\r
1785         "{}",\r
1786         "m.dummy");\r
1787 }\r
1788 \r
1789 bool\r
1790 MatrixClientFindDevice(\r
1791     MatrixClient * client,\r
1792     const char * deviceId,\r
1793     MatrixDevice ** outDevice)\r
1794 {\r
1795     for (int i = 0; i < client->numDevices; i++)\r
1796     {\r
1797         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
1798         {\r
1799             *outDevice = &client->devices[i];\r
1800             return true;\r
1801         }\r
1802     }\r
1803 \r
1804     MatrixClientRequestDeviceKeys(client);\r
1805 \r
1806     for (int i = 0; i < client->numDevices; i++)\r
1807     {\r
1808         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
1809         {\r
1810             *outDevice = &client->devices[i];\r
1811             return true;\r
1812         }\r
1813     }\r
1814 \r
1815     *outDevice = NULL;\r
1816     return false;\r
1817 }\r
1818 \r
1819 bool\r
1820 MatrixClientRequestDeviceKey(\r
1821     MatrixClient * client,\r
1822     const char * deviceId,\r
1823     char * outDeviceKey, int outDeviceKeyCap)\r
1824 {\r
1825     MatrixDevice * device;\r
1826     \r
1827     if (MatrixClientFindDevice(client, deviceId, &device))\r
1828     {\r
1829         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1830         return true;\r
1831     }\r
1832 \r
1833     MatrixClientRequestDeviceKeys(client);\r
1834     \r
1835     if (MatrixClientFindDevice(client, deviceId, &device))\r
1836     {\r
1837         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1838         return true;\r
1839     }\r
1840 \r
1841     return false;\r
1842 }\r
1843 \r
1844 bool\r
1845 MatrixClientRequestSigningKey(\r
1846     MatrixClient * client,\r
1847     const char * deviceId,\r
1848     char * outSigningKey, int outSigningKeyCap)\r
1849 {\r
1850     MatrixDevice * device;\r
1851     \r
1852     if (MatrixClientFindDevice(client, deviceId, &device))\r
1853     {\r
1854         strncpy(outSigningKey, device->signingKey, outSigningKeyCap);\r
1855         return true;\r
1856     }\r
1857 \r
1858     MatrixClientRequestDeviceKeys(client);\r
1859     \r
1860     if (MatrixClientFindDevice(client, deviceId, &device))\r
1861     {\r
1862         strncpy(outSigningKey, device->signingKey, outSigningKeyCap);\r
1863         return true;\r
1864     }\r
1865 \r
1866     return false;\r
1867 }\r
1868 \r
1869 bool\r
1870 MatrixClientRequestMasterKey(\r
1871     MatrixClient * client,\r
1872     char * outMasterKey, int outMasterKeyCap)\r
1873 {\r
1874     if (strlen(client->masterKey) > 0) {\r
1875         strncpy(outMasterKey, outMasterKeyCap, client->masterKey);\r
1876         return true;\r
1877     }\r
1878 \r
1879     MatrixClientRequestDeviceKeys(client);\r
1880     \r
1881     if (strlen(client->masterKey) > 0) {\r
1882         strncpy(outMasterKey, outMasterKeyCap, client->masterKey);\r
1883         return true;\r
1884     }\r
1885 \r
1886     return false;\r
1887 }\r
1888 \r
1889 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery\r
1890 bool\r
1891 MatrixClientRequestDeviceKeys(\r
1892     MatrixClient * client)\r
1893 {\r
1894     if (client->numDevices >= NUM_DEVICES) {\r
1895         printf("Maximum number of devices reached\n");\r
1896         return false;\r
1897     }\r
1898 \r
1899     STATIC char userIdEscaped[USER_ID_SIZE];\r
1900     JsonEscape(client->userId, strlen(client->userId),\r
1901         userIdEscaped, USER_ID_SIZE);\r
1902 \r
1903     STATIC char request[KEYS_QUERY_REQUEST_SIZE];\r
1904     snprintf(request, KEYS_QUERY_REQUEST_SIZE,\r
1905         "{\"device_keys\":{\"%s\":[]}}", client->userId);\r
1906 \r
1907     STATIC char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];\r
1908     bool requestResult = MatrixHttpPost(client->hc,\r
1909         KEYS_QUERY_URL,\r
1910         request,\r
1911         responseBuffer, KEYS_QUERY_RESPONSE_SIZE,\r
1912         true);\r
1913 \r
1914     if (! requestResult)\r
1915         return false;\r
1916 \r
1917     // query for retrieving device keys for user id\r
1918     STATIC char query[JSON_QUERY_SIZE];\r
1919     const char * s;\r
1920     int slen;\r
1921 \r
1922     snprintf(query, JSON_QUERY_SIZE,\r
1923         "$.master_keys.%s.keys", userIdEscaped);\r
1924     mjson_find(responseBuffer, strlen(responseBuffer),\r
1925         query, &s, &slen);\r
1926     \r
1927     int koff, klen, voff, vlen, vtype, off = 0;\r
1928     for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
1929                                     &voff, &vlen, &vtype)) != 0; ) {\r
1930         snprintf(client->masterKey, MASTER_KEY_SIZE,\r
1931             "%.*s", vlen-2, s+voff+1);\r
1932 \r
1933         printf("found master key: %s\n", client->masterKey);\r
1934     }\r
1935 \r
1936     snprintf(query, JSON_QUERY_SIZE,\r
1937         "$.device_keys.%s", userIdEscaped);\r
1938     \r
1939     mjson_find(responseBuffer, strlen(responseBuffer),\r
1940         query, &s, &slen);\r
1941     \r
1942     // loop over keys\r
1943     \r
1944     for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
1945                                     &voff, &vlen, &vtype)) != 0; ) {\r
1946         const char * key = s + koff;\r
1947         const char * val = s + voff;\r
1948 \r
1949         // set device id, "key" is the JSON key\r
1950         MatrixDevice d;\r
1951         snprintf(d.deviceId, DEVICE_ID_SIZE,\r
1952             "%.*s", klen-2, key+1);\r
1953 \r
1954         // look for device key in value\r
1955         STATIC char deviceKeyQuery[JSON_QUERY_SIZE];\r
1956         snprintf(deviceKeyQuery, JSON_QUERY_SIZE,\r
1957             "$.keys.curve25519:%s", d.deviceId);\r
1958         mjson_get_string(val, vlen,\r
1959             deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);\r
1960 \r
1961         // look for signing key in value\r
1962         STATIC char signingKeyQuery[JSON_QUERY_SIZE];\r
1963         snprintf(signingKeyQuery, JSON_QUERY_SIZE,\r
1964             "$.keys.ed25519:%s", d.deviceId);\r
1965         mjson_get_string(val, vlen,\r
1966             signingKeyQuery, d.signingKey, SIGNING_KEY_SIZE);\r
1967 \r
1968         // add device\r
1969         if (client->numDevices < NUM_DEVICES)\r
1970         {\r
1971             bool foundDevice = false;\r
1972             for (int i = 0; i < client->numDevices; i++)\r
1973                 if (strcmp(client->devices[i].deviceId, d.deviceId) == 0)\r
1974                     foundDevice = true;\r
1975 \r
1976             if (! foundDevice) {\r
1977                 printf("new device: %s %s %s\n", d.deviceId, d.deviceKey, d.signingKey);\r
1978                 client->devices[client->numDevices] = d;\r
1979                 client->numDevices++;\r
1980             }\r
1981         }\r
1982         else\r
1983         {\r
1984             return false;\r
1985         }\r
1986     }\r
1987 \r
1988     return true;\r
1989 }\r
1990 \r
1991 bool\r
1992 MatrixClientDeleteDevice(\r
1993     MatrixClient * client)\r
1994 {\r
1995     STATIC char deleteRequest[1024];\r
1996     snprintf(deleteRequest, 1024,\r
1997         "{\"devices\":[\"%s\"]}",\r
1998         client->deviceId);\r
1999     STATIC char deleteResponse[1024];\r
2000     bool res = MatrixHttpPost(client->hc, "/_matrix/client/v3/delete_devices",\r
2001         deleteRequest, deleteResponse, 1024, true);\r
2002     return res;\r
2003 }\r