]> gitweb.ps.run Git - matrix_esp_thesis/blob - src/matrix.c
actually add Canonical and Verify examples :()
[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 (1024*5)\r
13 #define ENCRYPTED_EVENT_SIZE (1024*10)\r
14 #define ROOM_SEND_REQUEST_SIZE 256\r
15 #define ROOM_SEND_RESPONSE_SIZE 1024\r
16 #define ROOM_SEND_URL "/_matrix/client/v3/rooms/%s/send/%s/%d"\r
17 \r
18 #define ROOMKEY_REQUEST_SIZE (1024*4)\r
19 \r
20 #define TODEVICE_EVENT_SIZE (1024*5)\r
21 #define TODEVICE_URL "/_matrix/client/v3/sendToDevice/%s/%d"\r
22 \r
23 #define KEYS_QUERY_URL "/_matrix/client/v3/keys/query"\r
24 #define KEYS_QUERY_REQUEST_SIZE 256\r
25 #define KEYS_QUERY_RESPONSE_SIZE (1024*10)\r
26 \r
27 #define KEYS_UPLOAD_URL "/_matrix/client/v3/keys/upload"\r
28 #define KEYS_UPLOAD_REQUEST_SIZE 1024*4\r
29 #define KEYS_UPLOAD_REQUEST_SIGNED_SIZE 2048*4\r
30 #define KEYS_UPLOAD_RESPONSE_SIZE 2048\r
31 \r
32 #define KEYS_CLAIM_URL "/_matrix/client/v3/keys/claim"\r
33 #define KEYS_CLAIM_REQUEST_SIZE 1024\r
34 #define KEYS_CLAIM_RESPONSE_SIZE 1024\r
35 \r
36 #define SYNC_TIMEOUT 5000\r
37 \r
38 #define JSON_QUERY_SIZE 128\r
39 #define JSON_MAX_INDICES 100\r
40 #define JSON_MAX_ENTRY_SIZE 1024\r
41 \r
42 #define MAX(a,b) ((a) > (b) ? (a) : (b))\r
43 #define MIN(a,b) ((a) < (b) ? (a) : (b))\r
44 \r
45 void\r
46 Randomize(\r
47     uint8_t * random,\r
48     int randomLen)\r
49 {\r
50     static bool first = true;\r
51     if (first) { srand(time(0)); first = false; }\r
52 \r
53     for (int i = 0; i < randomLen; i++)\r
54     {\r
55         random[i] = rand() % 256;\r
56     }\r
57 }\r
58 \r
59 bool\r
60 JsonEscape(\r
61     const char * sIn, int sInLen,\r
62     char * sOut, int sOutCap)\r
63 {\r
64     int sOutIndex = 0;\r
65 \r
66     for (int i = 0; i < sInLen; i++)\r
67     {\r
68         if (i >= sOutCap)\r
69             return false;\r
70         \r
71         if (sIn[i] == '.' ||\r
72             sIn[i] == '[' ||\r
73             sIn[i] == ']'\r
74         ) {\r
75             sOut[sOutIndex++] = '\\';\r
76         }\r
77         sOut[sOutIndex++] = sIn[i];\r
78     }\r
79 \r
80     if (sOutIndex < sOutCap)\r
81         sOut[sOutIndex] = '\0';\r
82 \r
83     return true;\r
84 }\r
85 \r
86 bool\r
87 JsonCanonicalize(\r
88     const char * sIn, int sInLen,\r
89     char * sOut, int sOutCap)\r
90 {\r
91     snprintf(sOut, sOutCap, "{}");\r
92 \r
93     int koff, klen, voff, vlen, vtype, off;\r
94 \r
95     struct Key {\r
96         const char * ptr;\r
97         int len;\r
98     };\r
99 \r
100     struct Key keys[JSON_MAX_INDICES];\r
101     int numKeys = 0;\r
102 \r
103     for (off = 0; (off = mjson_next(sIn, sInLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0; ) {\r
104         keys[numKeys].ptr = sIn + koff;\r
105         keys[numKeys].len = klen;\r
106         numKeys++;\r
107     }\r
108 \r
109     for (int i = 0; i < numKeys; i++) {\r
110         for (int j = i; j < numKeys; j++) {\r
111             if (\r
112                 strncmp(\r
113                     keys[i].ptr,\r
114                     keys[j].ptr,\r
115                     MIN(keys[i].len, keys[j].len)\r
116                 ) > 0\r
117             ) {\r
118                 struct Key k = keys[i];\r
119                 keys[i] = keys[j];\r
120                 keys[j] = k;\r
121             }\r
122         }\r
123     }\r
124 \r
125     for (int i = 0; i < numKeys; i++) {\r
126         char jp[JSON_QUERY_SIZE];\r
127         snprintf(jp, JSON_QUERY_SIZE, "$.%.*s", keys[i].len-2, keys[i].ptr+1);\r
128 \r
129         const char * valPtr;\r
130         int valLen;\r
131         mjson_find(sIn, sInLen, jp, &valPtr, &valLen);\r
132         \r
133         static char newEntry[JSON_MAX_ENTRY_SIZE];\r
134         snprintf(newEntry, JSON_MAX_ENTRY_SIZE, "{%.*s:%.*s}", keys[i].len, keys[i].ptr, valLen, valPtr);\r
135 \r
136         char * buffer = strdup(sOut);\r
137 \r
138         struct mjson_fixedbuf fb = { sOut, sOutCap, 0 };\r
139         mjson_merge(buffer, strlen(buffer), newEntry, strlen(newEntry), mjson_print_fixed_buf, &fb);\r
140 \r
141         free(buffer);\r
142     }\r
143 \r
144     // TODO: recursively sort entries\r
145 \r
146     return true;\r
147 }\r
148 \r
149 bool JsonSign(\r
150     MatrixClient * client,\r
151     const char * sIn, int sInLen,\r
152     char * sOut, int sOutCap)\r
153 {\r
154     static char signature[OLM_SIGNATURE_SIZE];\r
155     size_t res =\r
156         olm_account_sign(client->olmAccount.account,\r
157             sIn, sInLen,\r
158             signature, OLM_SIGNATURE_SIZE);\r
159     \r
160     int signatureLen = res;\r
161     \r
162     static char thisSigningKey[SIGNING_KEY_SIZE];\r
163     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, SIGNING_KEY_SIZE);\r
164 \r
165     static char signatureJson[JSON_SIGNATURE_SIZE];\r
166     int signatureJsonLen =\r
167         mjson_snprintf(signatureJson, JSON_SIGNATURE_SIZE,\r
168             "{"\r
169                 "\"signatures\":{"\r
170                     "\"%s\":{"\r
171                         "\"ed25519:%s\":\"%.*s\""\r
172                     "}"\r
173                 "}"\r
174             "}",\r
175             client->userId,\r
176             //"1",\r
177             client->deviceId,\r
178             signatureLen, signature);\r
179 \r
180     struct mjson_fixedbuf result = { sOut, sOutCap, 0 };\r
181     mjson_merge(\r
182         sIn, sInLen,\r
183         signatureJson, signatureJsonLen,\r
184         mjson_print_fixed_buf,\r
185         &result);\r
186 \r
187     return true;\r
188 }\r
189 \r
190 \r
191 bool\r
192 MatrixOlmAccountInit(\r
193     MatrixOlmAccount * account)\r
194 {\r
195     account->account = olm_account(account->memory);\r
196 \r
197     static uint8_t random[OLM_ACCOUNT_RANDOM_SIZE];\r
198     Randomize(random, OLM_ACCOUNT_RANDOM_SIZE);\r
199 \r
200     size_t res = olm_create_account(\r
201         account->account,\r
202         random,\r
203         OLM_ACCOUNT_RANDOM_SIZE);\r
204 \r
205     return res != olm_error();\r
206 }\r
207 \r
208 bool\r
209 MatrixOlmAccountUnpickle(\r
210     MatrixOlmAccount * account,\r
211     void * pickled, int pickledLen,\r
212     const void * key, int keyLen)\r
213 {\r
214     size_t res;\r
215     res = olm_unpickle_account(account->account,\r
216         key, keyLen,\r
217         pickled, pickledLen);\r
218     if (res == olm_error()) {\r
219         printf("error unpickling olm account:%s\n",\r
220             olm_account_last_error(account->account));\r
221     }\r
222     return res != olm_error();\r
223 }\r
224 \r
225 bool\r
226 MatrixOlmAccountGetDeviceKey(\r
227     MatrixOlmAccount * account,\r
228     char * key, int keyCap)\r
229 {\r
230     static char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
231     size_t res =\r
232         olm_account_identity_keys(account->account,\r
233             deviceKeysJson, OLM_IDENTITY_KEYS_JSON_SIZE);\r
234     mjson_get_string(deviceKeysJson, res,\r
235         "$.curve25519",\r
236         key, keyCap);\r
237     return true;\r
238 }\r
239 \r
240 bool\r
241 MatrixOlmAccountGetSigningKey(\r
242     MatrixOlmAccount * account,\r
243     char * key, int keyCap)\r
244 {\r
245     static char deviceKeysJson[OLM_IDENTITY_KEYS_JSON_SIZE];\r
246     size_t res =\r
247         olm_account_identity_keys(account->account,\r
248             deviceKeysJson, OLM_IDENTITY_KEYS_JSON_SIZE);\r
249     mjson_get_string(deviceKeysJson, res,\r
250         "$.ed25519",\r
251         key, keyCap);\r
252     return true;\r
253 }\r
254 \r
255 bool\r
256 MatrixOlmSessionFrom(\r
257     MatrixOlmSession * session,\r
258     OlmAccount * olmAccount,\r
259     const char * deviceId,\r
260     const char * deviceKey,\r
261     const char * encrypted)\r
262 {\r
263     memset(session, 0, sizeof(MatrixOlmSession));\r
264 \r
265     session->deviceId = deviceId;\r
266 \r
267     session->session =\r
268         olm_session(session->memory);\r
269     \r
270     char * encryptedCopy = strdup(encrypted);\r
271 \r
272     size_t res =\r
273         olm_create_inbound_session_from(session->session, olmAccount,\r
274             deviceKey, strlen(deviceKey),\r
275             encryptedCopy, strlen(encryptedCopy));\r
276     \r
277     if (res == olm_error()) {\r
278         printf("error olm:%s\n", olm_session_last_error(session->session));\r
279     }\r
280 \r
281     return res != olm_error();\r
282 }\r
283 \r
284 bool\r
285 MatrixOlmSessionTo(\r
286     MatrixOlmSession * session,\r
287     OlmAccount * olmAccount,\r
288     const char * deviceId,\r
289     const char * deviceKey,\r
290     const char * deviceOnetimeKey)\r
291 {\r
292     memset(session, 0, sizeof(MatrixOlmSession));\r
293 \r
294     session->deviceId = deviceId;\r
295 \r
296     session->session =\r
297         olm_session(session->memory);\r
298 \r
299     static uint8_t random[OLM_OUTBOUND_SESSION_RANDOM_SIZE];\r
300     Randomize(random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);\r
301 \r
302     size_t res =\r
303         olm_create_outbound_session(session->session,\r
304             olmAccount,\r
305             deviceKey, strlen(deviceKey),\r
306             deviceOnetimeKey, strlen(deviceOnetimeKey),\r
307             random, OLM_OUTBOUND_SESSION_RANDOM_SIZE);\r
308     \r
309     if (res == olm_error()) {\r
310         printf("error olm:%s\n", olm_session_last_error(session->session));\r
311     }\r
312 \r
313     return res != olm_error();\r
314 }\r
315 \r
316 bool\r
317 MatrixOlmSessionUnpickle(\r
318     MatrixOlmSession * session,\r
319     const char * deviceId,\r
320     void * pickled, int pickledLen,\r
321     const void * key, int keyLen)\r
322 {\r
323     memset(session, 0, sizeof(MatrixOlmSession));\r
324 \r
325     session->deviceId = deviceId;\r
326 \r
327     session->session =\r
328         olm_session(session->memory);\r
329     \r
330     size_t res;\r
331     res = olm_unpickle_session(session->session,\r
332         key, keyLen,\r
333         pickled, pickledLen);\r
334     \r
335     if (res == olm_error()) {\r
336         printf("error unpickling olm session:%s\n", olm_session_last_error(session->session));\r
337     }\r
338 \r
339     return res != olm_error();\r
340 }\r
341 \r
342 bool\r
343 MatrixOlmSessionEncrypt(\r
344     MatrixOlmSession * session,\r
345     const char * plaintext,\r
346     char * outBuffer, int outBufferCap)\r
347 {\r
348     static uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
349     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
350 \r
351     size_t res = olm_encrypt(session->session,\r
352         plaintext, strlen(plaintext),\r
353         random, OLM_ENCRYPT_RANDOM_SIZE,\r
354         outBuffer, outBufferCap);\r
355 \r
356     return res != olm_error();\r
357 }\r
358 \r
359 bool\r
360 MatrixOlmSessionDecrypt(\r
361     MatrixOlmSession * session,\r
362     size_t messageType,\r
363     char * encrypted,\r
364     char * outBuffer, int outBufferCap)\r
365 {\r
366     static uint8_t random[OLM_ENCRYPT_RANDOM_SIZE];\r
367     Randomize(random, OLM_ENCRYPT_RANDOM_SIZE);\r
368 \r
369     size_t res =\r
370         olm_decrypt(session->session,\r
371             messageType,\r
372             encrypted, strlen(encrypted),\r
373             outBuffer, outBufferCap);\r
374     \r
375     if (res != olm_error() && res < outBufferCap)\r
376         outBuffer[res] = '\0';\r
377 \r
378     return res != olm_error();\r
379 }\r
380 \r
381 bool\r
382 MatrixMegolmInSessionInit(\r
383     MatrixMegolmInSession * session,\r
384     const char * roomId,\r
385     const char * sessionId,\r
386     const char * sessionKey, int sessionKeyLen)\r
387 {\r
388     memset(session, 0, sizeof(MatrixMegolmInSession));\r
389     \r
390     strncpy(session->roomId, roomId, sizeof(session->roomId));\r
391     strncpy(session->id, sessionId, sizeof(session->id));\r
392     strncpy(session->key, sessionKey, sizeof(session->key));\r
393 \r
394     session->session =\r
395         olm_inbound_group_session(session->memory);\r
396 \r
397     size_t res =\r
398         olm_init_inbound_group_session(\r
399         // olm_import_inbound_group_session(\r
400             session->session,\r
401             (const uint8_t *)sessionKey, sessionKeyLen);\r
402     if (res == olm_error()) {\r
403         printf("Error initializing Megolm session: %s\n", olm_inbound_group_session_last_error(session->session));\r
404     }\r
405 \r
406     return res != olm_error();\r
407 }\r
408 \r
409 bool\r
410 MatrixMegolmInSessionDecrypt(\r
411     MatrixMegolmInSession * session,\r
412     const char * encrypted, int encryptedLen,\r
413     char * outDecrypted, int outDecryptedCap)\r
414 {\r
415     // uint8_t buffer[1024];\r
416     // memcpy(buffer, encrypted, encryptedLen);\r
417 \r
418     uint32_t megolmInMessageIndex;\r
419 \r
420     size_t res =\r
421         olm_group_decrypt(session->session,\r
422             (uint8_t *)encrypted, encryptedLen,\r
423             (uint8_t *)outDecrypted, outDecryptedCap,\r
424             &megolmInMessageIndex);\r
425     \r
426     printf("message index: %d\n", megolmInMessageIndex);\r
427     \r
428     if (res == olm_error()) {\r
429         printf("error decrypting megolm message: %s\n", olm_inbound_group_session_last_error(session->session));\r
430     }\r
431     else {\r
432         printf("decrypted len: %d\n", res);\r
433     }\r
434     \r
435     return true;\r
436 }\r
437 \r
438 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#starting-a-megolm-session\r
439 bool\r
440 MatrixMegolmOutSessionInit(\r
441     MatrixMegolmOutSession * session,\r
442     const char * roomId)\r
443 {\r
444     memset(session, 0, sizeof(MatrixMegolmOutSession));\r
445 \r
446     static uint8_t random[MEGOLM_INIT_RANDOM_SIZE];\r
447     Randomize(random, MEGOLM_INIT_RANDOM_SIZE);\r
448 \r
449     strncpy(session->roomId, roomId, ROOM_ID_SIZE);\r
450 \r
451     session->session =\r
452         olm_outbound_group_session(session->memory);\r
453 \r
454     olm_init_outbound_group_session(\r
455         session->session,\r
456         random,\r
457         MEGOLM_INIT_RANDOM_SIZE);\r
458 \r
459     olm_outbound_group_session_id(session->session,\r
460         (uint8_t *)session->id,\r
461         MEGOLM_SESSION_ID_SIZE);\r
462         \r
463     olm_outbound_group_session_key(session->session,\r
464         (uint8_t *)session->key,\r
465         MEGOLM_SESSION_KEY_SIZE);\r
466     \r
467     return true;\r
468 }\r
469 \r
470 bool\r
471 MatrixMegolmOutSessionEncrypt(\r
472     MatrixMegolmOutSession * session,\r
473     const char * plaintext,\r
474     char * outBuffer, int outBufferCap)\r
475 {\r
476     size_t res = olm_group_encrypt(session->session,\r
477         (uint8_t *)plaintext, strlen(plaintext),\r
478         (uint8_t *)outBuffer, outBufferCap);\r
479 \r
480     return res != olm_error();\r
481 }\r
482 \r
483 bool\r
484 MatrixMegolmOutSessionSave(\r
485     MatrixMegolmOutSession * session,\r
486     const char * filename,\r
487     const char * key)\r
488 {\r
489     FILE * f = fopen(filename, "w");\r
490 \r
491     size_t roomIdLen = strlen(session->roomId);\r
492     fwrite(&roomIdLen, sizeof(size_t), 1, f);\r
493     fwrite(session->roomId, 1, roomIdLen, f);\r
494 \r
495     size_t pickleBufferLen =\r
496         olm_pickle_outbound_group_session_length(\r
497             session->session);\r
498     void * pickleBuffer = malloc(pickleBufferLen);\r
499 \r
500     olm_pickle_outbound_group_session(\r
501         session->session,\r
502         key, strlen(key),\r
503         pickleBuffer, pickleBufferLen);\r
504     \r
505     fwrite(&pickleBufferLen, sizeof(size_t), 1, f);\r
506     fwrite(pickleBuffer, 1, pickleBufferLen, f);\r
507     free(pickleBuffer);\r
508 \r
509     fclose(f);\r
510 \r
511     return true;\r
512 }\r
513 \r
514 bool\r
515 MatrixMegolmOutSessionLoad(\r
516     MatrixMegolmOutSession * session,\r
517     const char * filename,\r
518     const char * key)\r
519 {\r
520     FILE * f = fopen(filename, "r");\r
521 \r
522     size_t roomIdLen;\r
523     fread(&roomIdLen, sizeof(size_t), 1, f);\r
524     fread(session->roomId, 1, roomIdLen, f);\r
525     for (int i = roomIdLen; i < ROOM_ID_SIZE; i++)\r
526         session->roomId[i] = '\0';\r
527 \r
528     size_t pickleBufferLen;\r
529     fread(&pickleBufferLen, sizeof(size_t), 1, f);\r
530 \r
531     void * pickleBuffer = malloc(pickleBufferLen);\r
532     fread(pickleBuffer, 1, pickleBufferLen, f);\r
533 \r
534     olm_unpickle_outbound_group_session(\r
535         session->session,\r
536         key, strlen(key),\r
537         pickleBuffer, pickleBufferLen);\r
538     \r
539     free(pickleBuffer);\r
540 \r
541     olm_outbound_group_session_id(session->session, (uint8_t *)session->id, MEGOLM_SESSION_ID_SIZE);\r
542     olm_outbound_group_session_key(session->session, (uint8_t *)session->key, MEGOLM_SESSION_KEY_SIZE);\r
543 \r
544     fclose(f);\r
545 \r
546     return true;\r
547 }\r
548 \r
549 \r
550 \r
551 bool\r
552 MatrixClientInit(\r
553     MatrixClient * client,\r
554     const char * server)\r
555 {\r
556     memset(client, 0, sizeof(MatrixClient));\r
557 \r
558     strcpy(client->server, server);\r
559 \r
560     // init olm account\r
561     MatrixOlmAccountInit(&client->olmAccount);\r
562 \r
563     return true;\r
564 }\r
565 \r
566 bool\r
567 MatrixClientSave(\r
568     MatrixClient * client,\r
569     const char * filename)\r
570 {\r
571     FILE * f = fopen(filename, "w");\r
572     \r
573     \r
574     char thisDeviceKey[DEVICE_KEY_SIZE];\r
575     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
576     char thisSigningKey[DEVICE_KEY_SIZE];\r
577     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);\r
578 \r
579 \r
580     fwrite(thisDeviceKey, 1, DEVICE_KEY_SIZE, f);\r
581     fwrite(thisSigningKey, 1, DEVICE_KEY_SIZE, f);\r
582     fwrite(client->userId, 1, USER_ID_SIZE, f);\r
583     fwrite(client->server, 1, SERVER_SIZE, f);\r
584     fwrite(client->accessToken, 1, ACCESS_TOKEN_SIZE, f);\r
585     fwrite(client->deviceId, 1, DEVICE_ID_SIZE, f);\r
586     fwrite(client->expireMs, 1, EXPIRE_MS_SIZE, f);\r
587     fwrite(client->refreshToken, 1, REFRESH_TOKEN_SIZE, f);\r
588 \r
589     fwrite(&client->numDevices, sizeof(int), 1, f);\r
590     for (int i = 0; i < client->numDevices; i++) {\r
591         fwrite(client->devices[i].deviceId, 1, DEVICE_ID_SIZE, f);\r
592         fwrite(client->devices[i].deviceKey, 1, DEVICE_KEY_SIZE, f);\r
593     }\r
594 \r
595     fclose(f);\r
596     return true;\r
597 }\r
598 \r
599 bool\r
600 MatrixClientLoad(\r
601     MatrixClient * client,\r
602     const char * filename)\r
603 {\r
604     FILE * f = fopen(filename, "r");\r
605     \r
606     \r
607     char thisDeviceKey[DEVICE_KEY_SIZE];\r
608     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
609     char thisSigningKey[DEVICE_KEY_SIZE];\r
610     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);\r
611 \r
612 \r
613     fread(thisDeviceKey, 1, DEVICE_KEY_SIZE, f);\r
614     fread(thisSigningKey, 1, DEVICE_KEY_SIZE, f);\r
615     fread(client->userId, 1, USER_ID_SIZE, f);\r
616     fread(client->server, 1, SERVER_SIZE, f);\r
617     fread(client->accessToken, 1, ACCESS_TOKEN_SIZE, f);\r
618     fread(client->deviceId, 1, DEVICE_ID_SIZE, f);\r
619     fread(client->expireMs, 1, EXPIRE_MS_SIZE, f);\r
620     fread(client->refreshToken, 1, REFRESH_TOKEN_SIZE, f);\r
621 \r
622     fread(&client->numDevices, sizeof(int), 1, f);\r
623     for (int i = 0; i < client->numDevices; i++) {\r
624         fread(client->devices[i].deviceId, 1, DEVICE_ID_SIZE, f);\r
625         fread(client->devices[i].deviceKey, 1, DEVICE_KEY_SIZE, f);\r
626     }\r
627 \r
628     fclose(f);\r
629     return true;\r
630 }\r
631 \r
632 bool\r
633 MatrixClientSetAccessToken(\r
634     MatrixClient * client,\r
635     const char * accessToken)\r
636 {\r
637     for (int i = 0; i < ACCESS_TOKEN_SIZE-1; i++)\r
638         client->accessToken[i] = accessToken[i];\r
639     client->accessToken[ACCESS_TOKEN_SIZE-1] = '\0';\r
640 \r
641     return true;\r
642 }\r
643 \r
644 bool\r
645 MatrixClientSetDeviceId(\r
646     MatrixClient * client,\r
647     const char * deviceId)\r
648 {\r
649     for (int i = 0; i < DEVICE_ID_SIZE-1; i++)\r
650         client->deviceId[i] = deviceId[i];\r
651     client->deviceId[DEVICE_ID_SIZE-1] = '\0';\r
652 \r
653     return true;\r
654 }\r
655 \r
656 bool\r
657 MatrixClientSetUserId(\r
658     MatrixClient * client,\r
659     const char * userId)\r
660 {\r
661     for (int i = 0; i < USER_ID_SIZE-1; i++)\r
662         client->userId[i] = userId[i];\r
663     client->userId[USER_ID_SIZE-1] = '\0';\r
664 \r
665     return true;\r
666 }\r
667 \r
668 bool\r
669 MatrixClientGenerateOnetimeKeys(\r
670     MatrixClient * client,\r
671     int numberOfKeys)\r
672 {\r
673     static uint8_t random[OLM_ONETIME_KEYS_RANDOM_SIZE];\r
674     Randomize(random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
675 \r
676     size_t res =\r
677         olm_account_generate_one_time_keys(client->olmAccount.account,\r
678             numberOfKeys, random, OLM_ONETIME_KEYS_RANDOM_SIZE);\r
679 \r
680     return res != olm_error();\r
681 }\r
682 \r
683 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
684 bool\r
685 MatrixClientUploadOnetimeKeys(\r
686     MatrixClient * client)\r
687 {\r
688     static char requestBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
689 \r
690     mjson_snprintf(requestBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
691         "{");\r
692 \r
693     static char onetimeKeysBuffer[1024];\r
694     olm_account_one_time_keys(client->olmAccount.account,\r
695         onetimeKeysBuffer, 1024);\r
696 \r
697     const char *keys;\r
698     int keysLen;\r
699     mjson_find(onetimeKeysBuffer, strlen(onetimeKeysBuffer), "$.curve25519", &keys, &keysLen);\r
700 \r
701     int koff, klen, voff, vlen, vtype, off = 0;\r
702     while ((off = mjson_next(keys, keysLen, off, &koff, &klen, &voff, &vlen, &vtype)) != 0) {\r
703         static char keyJson[JSON_ONETIME_KEY_SIZE];\r
704         \r
705         int keyJsonLen =\r
706             snprintf(keyJson, JSON_ONETIME_KEY_SIZE,\r
707                 "{\"key\":\"%.*s\"}",\r
708                 vlen-2, keys + voff+1);\r
709 \r
710         static char keyJsonSigned[JSON_ONETIME_KEY_SIGNED_SIZE];\r
711 \r
712         JsonSign(client,\r
713             keyJson, keyJsonLen,\r
714             keyJsonSigned, JSON_ONETIME_KEY_SIGNED_SIZE);\r
715         \r
716         mjson_snprintf(requestBuffer+strlen(requestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(requestBuffer),\r
717             "\"signed_curve25519:%.*s\":%s,",\r
718             klen-2, keys + koff+1,\r
719             keyJsonSigned);\r
720     }\r
721 \r
722     if (requestBuffer[strlen(requestBuffer)-1] == ',')\r
723         requestBuffer[strlen(requestBuffer)-1] = '\0';\r
724 \r
725     mjson_snprintf(requestBuffer+strlen(requestBuffer), KEYS_UPLOAD_REQUEST_SIZE-strlen(requestBuffer),\r
726         "}");\r
727         \r
728     // static char onetimeKeysSignedBuffer[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
729     // JsonSign(client,\r
730     //     requestBuffer, strlen(requestBuffer),\r
731     //     onetimeKeysSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);\r
732         \r
733     // static char finalEvent[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
734     // snprintf(finalEvent, KEYS_UPLOAD_REQUEST_SIGNED_SIZE,\r
735     // "{\"one_time_keys\":%s}", onetimeKeysSignedBuffer);\r
736     static char finalEvent[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
737     snprintf(finalEvent, KEYS_UPLOAD_REQUEST_SIGNED_SIZE,\r
738     "{\"one_time_keys\":%s}", requestBuffer);\r
739 \r
740     static char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
741     MatrixHttpPost(client,\r
742         KEYS_UPLOAD_URL,\r
743         finalEvent,\r
744         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
745         true);\r
746 \r
747     return true;\r
748 }\r
749 \r
750 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysupload\r
751 bool\r
752 MatrixClientUploadDeviceKey(\r
753     MatrixClient * client)\r
754 {\r
755     char thisDeviceKey[DEVICE_KEY_SIZE];\r
756     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
757     char thisSigningKey[DEVICE_KEY_SIZE];\r
758     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);\r
759 \r
760     static char deviceKeysBuffer[KEYS_UPLOAD_REQUEST_SIZE];\r
761 \r
762     int deviceKeysBufferLen =\r
763         mjson_snprintf(deviceKeysBuffer, KEYS_UPLOAD_REQUEST_SIZE,\r
764             "{"\r
765                 "\"algorithms\":[\"m.olm.v1.curve25519-aes-sha2\",\"m.megolm.v1.aes-sha2\"],"\r
766                 "\"device_id\":\"%s\","\r
767                 "\"keys\":{"\r
768                     "\"curve25519:%s\":\"%s\","\r
769                     "\"ed25519:%s\":\"%s\""\r
770                 "},"\r
771                 "\"user_id\":\"%s\""\r
772             "}",\r
773             client->deviceId,\r
774             client->deviceId, thisDeviceKey,\r
775             client->deviceId, thisSigningKey,\r
776             client->userId);\r
777 \r
778     static char deviceKeysSignedBuffer[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
779     JsonSign(client,\r
780         deviceKeysBuffer, deviceKeysBufferLen,\r
781         deviceKeysSignedBuffer, KEYS_UPLOAD_REQUEST_SIZE);\r
782     \r
783     static char finalEvent[KEYS_UPLOAD_REQUEST_SIGNED_SIZE];\r
784     snprintf(finalEvent, KEYS_UPLOAD_REQUEST_SIGNED_SIZE,\r
785     "{\"device_keys\":%s}", deviceKeysSignedBuffer);\r
786 \r
787     static char responseBuffer[KEYS_UPLOAD_RESPONSE_SIZE];\r
788     MatrixHttpPost(client,\r
789         KEYS_UPLOAD_URL,\r
790         finalEvent,\r
791         responseBuffer, KEYS_UPLOAD_RESPONSE_SIZE,\r
792         true);\r
793 \r
794     return true;\r
795 }\r
796 \r
797 // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysclaim\r
798 bool\r
799 MatrixClientClaimOnetimeKey(\r
800     MatrixClient * client,\r
801     const char * userId,\r
802     const char * deviceId,\r
803     char * outOnetimeKey, int outOnetimeKeyCap)\r
804 {\r
805     static char requestBuffer[KEYS_CLAIM_REQUEST_SIZE];\r
806     mjson_snprintf(requestBuffer, KEYS_CLAIM_REQUEST_SIZE,\r
807     "{"\r
808       "\"one_time_keys\":{"\r
809         "\"%s\":{"\r
810           "\"%s\":\"signed_curve25519\""\r
811         "}"\r
812       "},"\r
813       "\"timeout\":10000"\r
814     "}",\r
815     userId,\r
816     deviceId);\r
817 \r
818     static char responseBuffer[KEYS_CLAIM_RESPONSE_SIZE];\r
819     MatrixHttpPost(client,\r
820         KEYS_CLAIM_URL,\r
821         requestBuffer,\r
822         responseBuffer, KEYS_CLAIM_RESPONSE_SIZE,\r
823         true);\r
824     \r
825     char userIdEscaped[USER_ID_SIZE];\r
826     JsonEscape(userId, strlen(userId),\r
827         userIdEscaped, USER_ID_SIZE);\r
828     \r
829     static char query[JSON_QUERY_SIZE];\r
830     snprintf(query, JSON_QUERY_SIZE,\r
831         "$.one_time_keys.%s.%s",\r
832         userIdEscaped,\r
833         deviceId);\r
834     \r
835     const char * keyObject;\r
836     int keyObjectSize;\r
837     mjson_find(responseBuffer, strlen(responseBuffer),\r
838         query,\r
839         &keyObject, &keyObjectSize);\r
840     \r
841     int koff, klen, voff, vlen, vtype;\r
842     mjson_next(keyObject, keyObjectSize, 0,\r
843         &koff, &klen, &voff, &vlen, &vtype);\r
844     \r
845     mjson_get_string(keyObject + voff, vlen,\r
846         "$.key", outOnetimeKey, outOnetimeKeyCap);\r
847     \r
848     // TODO:verify signature\r
849     \r
850     return true;\r
851 }\r
852 \r
853 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3login\r
854 bool\r
855 MatrixClientLoginPassword(\r
856     MatrixClient * client,\r
857     const char * username,\r
858     const char * password,\r
859     const char * displayName)\r
860 {\r
861     static char requestBuffer[LOGIN_REQUEST_SIZE];\r
862 \r
863     mjson_snprintf(requestBuffer, LOGIN_REQUEST_SIZE,\r
864         "{"\r
865             "\"type\":\"m.login.password\","\r
866             "\"identifier\":{"\r
867                 "\"type\":\"m.id.user\","\r
868                 "\"user\":\"%s\""\r
869             "},"\r
870             "\"password\":\"%s\","\r
871             "\"initial_device_display_name\":\"%s\""\r
872         "}",\r
873         username,\r
874         password,\r
875         displayName);\r
876     \r
877     static char responseBuffer[LOGIN_RESPONSE_SIZE];\r
878     bool result =\r
879         MatrixHttpPost(client,\r
880             LOGIN_URL,\r
881             requestBuffer,\r
882             responseBuffer, LOGIN_RESPONSE_SIZE,\r
883             false);\r
884     \r
885     if (!result)\r
886         return false;\r
887     \r
888     int responseLen = strlen(responseBuffer);\r
889 \r
890     mjson_get_string(responseBuffer, responseLen,\r
891         "$.access_token",\r
892         client->accessToken, ACCESS_TOKEN_SIZE);\r
893     mjson_get_string(responseBuffer, responseLen,\r
894         "$.device_id",\r
895         client->deviceId, DEVICE_ID_SIZE);\r
896     mjson_get_string(responseBuffer, responseLen,\r
897         "$.expires_in_ms",\r
898         client->expireMs, EXPIRE_MS_SIZE);\r
899     mjson_get_string(responseBuffer, responseLen,\r
900         "$.refresh_token",\r
901         client->refreshToken, REFRESH_TOKEN_SIZE);\r
902 \r
903     return true;\r
904 }\r
905 \r
906 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid\r
907 bool\r
908 MatrixClientSendEvent(\r
909     MatrixClient * client,\r
910     const char * roomId,\r
911     const char * msgType,\r
912     const char * msgBody)\r
913 {    \r
914     static char requestUrl[MAX_URL_LEN];\r
915     sprintf(requestUrl,\r
916         ROOM_SEND_URL, roomId, msgType, (int)time(NULL));\r
917 \r
918     static char responseBuffer[ROOM_SEND_RESPONSE_SIZE];\r
919     bool result =\r
920         MatrixHttpPut(client,\r
921             requestUrl,\r
922             msgBody,\r
923             responseBuffer, ROOM_SEND_RESPONSE_SIZE,\r
924             true);\r
925     \r
926     return result;\r
927 }\r
928 \r
929 // https://spec.matrix.org/v1.6/client-server-api/#mroomencrypted\r
930 // https://matrix.org/docs/guides/end-to-end-encryption-implementation-guide#sending-an-encrypted-message-event\r
931 bool\r
932 MatrixClientSendEventEncrypted(\r
933     MatrixClient * client,\r
934     const char * roomId,\r
935     const char * msgType,\r
936     const char * msgBody)\r
937 {\r
938     // event json\r
939     static char requestBuffer[ROOM_SEND_REQUEST_SIZE];\r
940     sprintf(requestBuffer,\r
941         "{"\r
942         "\"type\":\"%s\","\r
943         "\"content\":%s,"\r
944         "\"room_id\":\"%s\""\r
945         "}",\r
946         msgType,\r
947         msgBody,\r
948         roomId);\r
949 \r
950     // get megolm session\r
951     MatrixMegolmOutSession * outSession;\r
952     if (! MatrixClientGetMegolmOutSession(client, roomId, &outSession))\r
953         MatrixClientNewMegolmOutSession(client, roomId, &outSession);\r
954         \r
955     // encrypt\r
956     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
957     MatrixMegolmOutSessionEncrypt(outSession,\r
958         requestBuffer,\r
959         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
960 \r
961     char thisDeviceKey[DEVICE_KEY_SIZE];\r
962     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
963     \r
964 \r
965     // encrypted event json\r
966     const char * senderKey = thisDeviceKey;\r
967     const char * sessionId = outSession->id;\r
968     const char * deviceId = client->deviceId;\r
969 \r
970     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
971     sprintf(encryptedEventBuffer,\r
972         "{"\r
973         "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
974         "\"ciphertext\":\"%s\","\r
975         "\"device_id\":\"%s\","\r
976         "\"sender_key\":\"%s\","\r
977         "\"session_id\":\"%s\""\r
978         "}",\r
979         encryptedBuffer,\r
980         deviceId,\r
981         senderKey,\r
982         sessionId);\r
983 \r
984     // send\r
985     return MatrixClientSendEvent(client,\r
986         roomId,\r
987         "m.room.encrypted",\r
988         encryptedEventBuffer);\r
989 }\r
990 \r
991 // https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3sync\r
992 bool\r
993 MatrixClientSync(\r
994     MatrixClient * client,\r
995     char * outSyncBuffer, int outSyncCap,\r
996     const char * nextBatch)\r
997 {\r
998     // filter={\"event_fields\":[\"to_device\"]}\r
999     static char url[MAX_URL_LEN];\r
1000     snprintf(url, MAX_URL_LEN,\r
1001         "/_matrix/client/v3/sync?timeout=%d%s",\r
1002         SYNC_TIMEOUT,\r
1003         strlen(nextBatch) > 0 ? "&since=" : "");\r
1004     \r
1005     int index = strlen(url);\r
1006 \r
1007     for (size_t i = 0; i < strlen(nextBatch); i++) {\r
1008         char c = nextBatch[i];\r
1009 \r
1010         if (c == '~') {\r
1011             url[index++] = '%';\r
1012             url[index++] = '7';\r
1013             url[index++] = 'E';\r
1014         }\r
1015         else {\r
1016             url[index++] = c;\r
1017         }\r
1018     }\r
1019     url[index] = '\0';\r
1020 \r
1021     return\r
1022         MatrixHttpGet(client,\r
1023             url,\r
1024             outSyncBuffer, outSyncCap,\r
1025             true);\r
1026 }\r
1027 \r
1028 // https://spec.matrix.org/v1.7/client-server-api/#get_matrixclientv3roomsroomideventeventid\r
1029 bool\r
1030 MatrixClientGetRoomEvent(\r
1031     MatrixClient * client,\r
1032     const char * roomId,\r
1033     const char * eventId,\r
1034     char * outEvent, int outEventCap)\r
1035 {\r
1036     static char url[MAX_URL_LEN];\r
1037     snprintf(url, MAX_URL_LEN,\r
1038         "/_matrix/client/v3/rooms/%s/event/%s",\r
1039             roomId,\r
1040             eventId);\r
1041 \r
1042     return\r
1043         MatrixHttpGet(client,\r
1044             url,\r
1045             outEvent, outEventCap,\r
1046             true);\r
1047 }\r
1048 \r
1049 bool\r
1050 MatrixClientShareMegolmOutSession(\r
1051     MatrixClient * client,\r
1052     const char * userId,\r
1053     const char * deviceId,\r
1054     MatrixMegolmOutSession * session)\r
1055 {\r
1056     // generate room key event\r
1057     static char eventBuffer[KEY_SHARE_EVENT_LEN];\r
1058     sprintf(eventBuffer,\r
1059         "{"\r
1060             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
1061             "\"room_id\":\"%s\","\r
1062             "\"session_id\":\"%s\","\r
1063             "\"session_key\":\"%s\""\r
1064         "}",\r
1065         session->roomId,\r
1066         session->id,\r
1067         session->key\r
1068     );\r
1069 \r
1070     // send\r
1071     MatrixClientSendToDeviceEncrypted(client,\r
1072         userId,\r
1073         deviceId,\r
1074         eventBuffer,\r
1075         "m.room_key");\r
1076 \r
1077     return true;\r
1078 }\r
1079 \r
1080 bool\r
1081 MatrixClientShareMegolmOutSessionTest(\r
1082     MatrixClient * client,\r
1083     const char * userId,\r
1084     const char * deviceId,\r
1085     MatrixMegolmOutSession * session)\r
1086 {\r
1087     // generate room key event\r
1088     char eventBuffer[KEY_SHARE_EVENT_LEN];\r
1089     sprintf(eventBuffer,\r
1090         "{"\r
1091             "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
1092             "\"room_id\":\"%s\","\r
1093             "\"session_id\":\"%s\","\r
1094             "\"session_key\":\"%s\""\r
1095         "}",\r
1096         session->roomId,\r
1097         session->id,\r
1098         session->key\r
1099     );\r
1100 \r
1101     // send\r
1102     MatrixClientSendToDevice(client,\r
1103         userId,\r
1104         deviceId,\r
1105         eventBuffer,\r
1106         "m.room_key");\r
1107 \r
1108     return true;\r
1109 }\r
1110 \r
1111 bool\r
1112 MatrixClientGetMegolmOutSession(\r
1113     MatrixClient * client,\r
1114     const char * roomId,\r
1115     MatrixMegolmOutSession ** outSession)\r
1116 {\r
1117     for (int i = 0; i < client->numMegolmOutSessions; i++)\r
1118     {\r
1119         if (strcmp(client->megolmOutSessions[i].roomId, roomId) == 0)\r
1120         {\r
1121             *outSession = &client->megolmOutSessions[i];\r
1122             return true;\r
1123         }\r
1124     }\r
1125 \r
1126     return false;\r
1127 }\r
1128 \r
1129 bool\r
1130 MatrixClientNewMegolmOutSession(\r
1131     MatrixClient * client,\r
1132     const char * roomId,\r
1133     MatrixMegolmOutSession ** outSession)\r
1134 {\r
1135     if (client->numMegolmOutSessions < NUM_MEGOLM_SESSIONS)\r
1136     {\r
1137         MatrixMegolmOutSession * result =\r
1138             &client->megolmOutSessions[client->numMegolmOutSessions];\r
1139         \r
1140         MatrixMegolmOutSessionInit(result,\r
1141             roomId);\r
1142 \r
1143         *outSession = result;\r
1144 \r
1145         client->numMegolmOutSessions++;\r
1146 \r
1147         return true;\r
1148     }\r
1149 \r
1150     return false;\r
1151 }\r
1152 \r
1153 bool\r
1154 MatrixClientGetMegolmInSession(\r
1155     MatrixClient * client,\r
1156     const char * roomId, int roomIdLen,\r
1157     const char * sessionId, int sessionIdLen,\r
1158     MatrixMegolmInSession ** outSession)\r
1159 {\r
1160     for (int i = 0; i < client->numMegolmInSessions; i++)\r
1161     {\r
1162         if (strncmp(client->megolmInSessions[i].roomId, roomId, roomIdLen) == 0 &&\r
1163             strncmp(client->megolmInSessions[i].id, sessionId, sessionIdLen) == 0)\r
1164         {\r
1165             *outSession = &client->megolmInSessions[i];\r
1166             return true;\r
1167         }\r
1168     }\r
1169 \r
1170     return false;\r
1171 }\r
1172 \r
1173 bool\r
1174 MatrixClientNewMegolmInSession(\r
1175     MatrixClient * client,\r
1176     const char * roomId,\r
1177     const char * sessionId,\r
1178     const char * sessionKey,\r
1179     MatrixMegolmInSession ** outSession)\r
1180 {\r
1181     if (client->numMegolmInSessions < NUM_MEGOLM_SESSIONS)\r
1182     {\r
1183         MatrixMegolmInSession * result =\r
1184             &client->megolmInSessions[client->numMegolmInSessions];\r
1185         \r
1186         MatrixMegolmInSessionInit(result,\r
1187             roomId,\r
1188             sessionId,\r
1189             sessionKey, strlen(sessionKey));\r
1190         \r
1191         *outSession = result;\r
1192 \r
1193         client->numMegolmInSessions++;\r
1194 \r
1195         return true;\r
1196     }\r
1197 \r
1198     return false;\r
1199 }\r
1200 \r
1201 bool\r
1202 MatrixClientRequestMegolmInSession(\r
1203     MatrixClient * client,\r
1204     const char * roomId,\r
1205     const char * sessionId,\r
1206     const char * senderKey,\r
1207     const char * userId,\r
1208     const char * deviceId)\r
1209 {\r
1210     // TODO: cancel requests\r
1211     MatrixClientSendDummy(client, userId, deviceId);\r
1212 \r
1213     static char event[ROOMKEY_REQUEST_SIZE];\r
1214     snprintf(event, ROOMKEY_REQUEST_SIZE,\r
1215         "{"\r
1216             "\"action\":\"request\","\r
1217             "\"body\":{"\r
1218                 "\"algorithm\":\"m.megolm.v1.aes-sha2\","\r
1219                 "\"room_id\":\"%s\","\r
1220                 "\"sender_key\":\"%s\","\r
1221                 "\"session_id\":\"%s\""\r
1222             "},"\r
1223             "\"request_id\":\"%d\","\r
1224             "\"requesting_device_id\":\"%s\""\r
1225         "}",\r
1226         roomId,\r
1227         senderKey,\r
1228         sessionId,\r
1229         time(NULL),\r
1230         client->deviceId);\r
1231 \r
1232     \r
1233     MatrixClientSendToDevice(client,\r
1234         userId,\r
1235         deviceId,\r
1236         event,\r
1237         "m.room_key_request");\r
1238 \r
1239     return true;\r
1240 }\r
1241 \r
1242 bool\r
1243 MatrixClientGetOlmSessionIn(\r
1244     MatrixClient * client,\r
1245     const char * userId,\r
1246     const char * deviceId,\r
1247     const char * encrypted,\r
1248     MatrixOlmSession ** outSession)\r
1249 {\r
1250     for (int i = 0; i < client->numOlmSessions; i++)\r
1251     {\r
1252         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
1253         {\r
1254             *outSession = &client->olmSessions[i];\r
1255             return true;\r
1256         }\r
1257     }\r
1258 \r
1259     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
1260     {\r
1261         static char deviceKey[DEVICE_KEY_SIZE];\r
1262         MatrixClientRequestDeviceKey(client,\r
1263             deviceId,\r
1264             deviceKey, DEVICE_KEY_SIZE);\r
1265 \r
1266         MatrixOlmSessionFrom(\r
1267             &client->olmSessions[client->numOlmSessions],\r
1268             client->olmAccount.account,\r
1269             deviceId,\r
1270             deviceKey,\r
1271             encrypted);\r
1272 \r
1273         *outSession = &client->olmSessions[client->numOlmSessions];\r
1274         \r
1275         client->numOlmSessions++;\r
1276 \r
1277         return true;\r
1278     }\r
1279 \r
1280     return false;\r
1281 }\r
1282 \r
1283 bool\r
1284 MatrixClientGetOlmSessionOut(\r
1285     MatrixClient * client,\r
1286     const char * userId,\r
1287     const char * deviceId,\r
1288     MatrixOlmSession ** outSession)\r
1289 {\r
1290     for (int i = 0; i < client->numOlmSessions; i++)\r
1291     {\r
1292         if (strcmp(client->olmSessions[i].deviceId, deviceId) == 0)\r
1293         {\r
1294             *outSession = &client->olmSessions[i];\r
1295             return true;\r
1296         }\r
1297     }\r
1298 \r
1299     if (client->numOlmSessions < NUM_OLM_SESSIONS)\r
1300     {\r
1301         static char deviceKey[DEVICE_KEY_SIZE];\r
1302         MatrixClientRequestDeviceKey(client,\r
1303             deviceId,\r
1304             deviceKey, DEVICE_KEY_SIZE);\r
1305 \r
1306         char onetimeKey[ONETIME_KEY_SIZE];\r
1307         MatrixClientClaimOnetimeKey(client,\r
1308             userId,\r
1309             deviceId,\r
1310             onetimeKey, ONETIME_KEY_SIZE);\r
1311 \r
1312         MatrixOlmSessionTo(\r
1313             &client->olmSessions[client->numOlmSessions],\r
1314             client->olmAccount.account,\r
1315             deviceId,\r
1316             deviceKey,\r
1317             onetimeKey);\r
1318 \r
1319         *outSession = &client->olmSessions[client->numOlmSessions];\r
1320         \r
1321         client->numOlmSessions++;\r
1322 \r
1323         return true;\r
1324     }\r
1325 \r
1326     return false;\r
1327 }\r
1328 \r
1329 // https://spec.matrix.org/v1.6/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid\r
1330 bool\r
1331 MatrixClientSendToDevice(\r
1332     MatrixClient * client,\r
1333     const char * userId,\r
1334     const char * deviceId,\r
1335     const char * message,\r
1336     const char * msgType)\r
1337 {\r
1338     static char requestUrl[MAX_URL_LEN];\r
1339     sprintf(requestUrl,\r
1340         TODEVICE_URL, msgType, (int)time(NULL));\r
1341 \r
1342     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
1343     snprintf(eventBuffer, TODEVICE_EVENT_SIZE,\r
1344         "{"\r
1345             "\"messages\":{"\r
1346                 "\"%s\":{"\r
1347                     "\"%s\":%s"\r
1348                 "}"\r
1349             "}"\r
1350         "}",\r
1351         userId,\r
1352         deviceId,\r
1353         message);\r
1354 \r
1355     static char responseBuffer[ROOM_SEND_RESPONSE_SIZE];\r
1356     bool result =\r
1357         MatrixHttpPut(client,\r
1358             requestUrl,\r
1359             eventBuffer,\r
1360             responseBuffer, ROOM_SEND_RESPONSE_SIZE,\r
1361             true);\r
1362     \r
1363     printf("%s\n", responseBuffer);\r
1364     \r
1365     return result;\r
1366 }\r
1367 \r
1368 bool\r
1369 MatrixClientSendToDeviceEncrypted(\r
1370     MatrixClient * client,\r
1371     const char * userId,\r
1372     const char * deviceId,\r
1373     const char * message,\r
1374     const char * msgType)\r
1375 {\r
1376     // get olm session\r
1377     MatrixOlmSession * olmSession;\r
1378     MatrixClientGetOlmSessionOut(client, userId, deviceId, &olmSession);\r
1379 \r
1380     // create event json\r
1381     char targetDeviceKey[DEVICE_KEY_SIZE];\r
1382     MatrixClientRequestDeviceKey(client, deviceId, targetDeviceKey, DEVICE_KEY_SIZE);\r
1383     char targetSigningKey[SIGNING_KEY_SIZE];\r
1384     MatrixClientRequestSigningKey(client, deviceId, targetSigningKey, SIGNING_KEY_SIZE);\r
1385     \r
1386     char thisSigningKey[DEVICE_KEY_SIZE];\r
1387     MatrixOlmAccountGetSigningKey(&client->olmAccount, thisSigningKey, DEVICE_KEY_SIZE);\r
1388 \r
1389     static char eventBuffer[TODEVICE_EVENT_SIZE];\r
1390     sprintf(eventBuffer,\r
1391         "{"\r
1392         "\"type\":\"%s\","\r
1393         "\"content\":%s,"\r
1394         "\"sender\":\"%s\","\r
1395         "\"recipient\":\"%s\","\r
1396         "\"recipient_keys\":{"\r
1397           "\"ed25519\":\"%s\""\r
1398         "},"\r
1399         "\"keys\":{"\r
1400           "\"ed25519\":\"%s\""\r
1401         "}"\r
1402         "}",\r
1403         msgType,\r
1404         message,\r
1405         client->userId,\r
1406         userId, // recipient user id\r
1407         targetSigningKey, // recipient device key\r
1408         thisSigningKey);\r
1409 \r
1410     // encrypt\r
1411     static char encryptedBuffer[ENCRYPTED_REQUEST_SIZE];\r
1412     MatrixOlmSessionEncrypt(olmSession,\r
1413         eventBuffer,\r
1414         encryptedBuffer, ENCRYPTED_REQUEST_SIZE);\r
1415 \r
1416     char thisDeviceKey[DEVICE_KEY_SIZE];\r
1417     MatrixOlmAccountGetDeviceKey(&client->olmAccount, thisDeviceKey, DEVICE_KEY_SIZE);\r
1418 \r
1419 \r
1420     static char encryptedEventBuffer[ENCRYPTED_EVENT_SIZE];\r
1421     sprintf(encryptedEventBuffer,\r
1422         "{"\r
1423         "\"algorithm\":\"m.olm.v1.curve25519-aes-sha2\","\r
1424         "\"ciphertext\":{"\r
1425           "\"%s\":{"\r
1426             "\"body\":\"%s\","\r
1427             "\"type\":%d"\r
1428           "}"\r
1429         "},"\r
1430         "\"device_id\":\"%s\","\r
1431         "\"sender_key\":\"%s\""\r
1432         "}",\r
1433         targetDeviceKey,\r
1434         encryptedBuffer,\r
1435         olm_session_has_received_message(olmSession->session),\r
1436         client->deviceId,\r
1437         thisDeviceKey);\r
1438 \r
1439     // send\r
1440     return MatrixClientSendToDevice(\r
1441         client,\r
1442         userId,\r
1443         deviceId,\r
1444         encryptedEventBuffer,\r
1445         "m.room.encrypted");\r
1446 }\r
1447 \r
1448 bool\r
1449 MatrixClientSendDummy(\r
1450     MatrixClient * client,\r
1451     const char * userId,\r
1452     const char * deviceId)\r
1453 {\r
1454     return MatrixClientSendToDeviceEncrypted(\r
1455         client,\r
1456         userId,\r
1457         deviceId,\r
1458         "{}",\r
1459         "m.dummy");\r
1460 }\r
1461 \r
1462 bool\r
1463 MatrixClientFindDevice(\r
1464     MatrixClient * client,\r
1465     const char * deviceId,\r
1466     MatrixDevice ** outDevice)\r
1467 {\r
1468     MatrixClientRequestDeviceKeys(client);\r
1469 \r
1470     for (int i = 0; i < client->numDevices; i++)\r
1471     {\r
1472         if (strcmp(client->devices[i].deviceId, deviceId) == 0)\r
1473         {\r
1474             *outDevice = &client->devices[i];\r
1475             return true;\r
1476         }\r
1477     }\r
1478 \r
1479     *outDevice = NULL;\r
1480     return false;\r
1481 }\r
1482 \r
1483 bool\r
1484 MatrixClientRequestDeviceKey(\r
1485     MatrixClient * client,\r
1486     const char * deviceId,\r
1487     char * outDeviceKey, int outDeviceKeyCap)\r
1488 {\r
1489     MatrixDevice * device;\r
1490     \r
1491     if (MatrixClientFindDevice(client, deviceId, &device))\r
1492     {\r
1493         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1494         return true;\r
1495     }\r
1496 \r
1497     MatrixClientRequestDeviceKeys(client);\r
1498     \r
1499     if (MatrixClientFindDevice(client, deviceId, &device))\r
1500     {\r
1501         strncpy(outDeviceKey, device->deviceKey, outDeviceKeyCap);\r
1502         return true;\r
1503     }\r
1504 \r
1505     return false;\r
1506 }\r
1507 \r
1508 bool\r
1509 MatrixClientRequestSigningKey(\r
1510     MatrixClient * client,\r
1511     const char * deviceId,\r
1512     char * outSigningKey, int outSigningKeyCap)\r
1513 {\r
1514     MatrixDevice * device;\r
1515     \r
1516     if (MatrixClientFindDevice(client, deviceId, &device))\r
1517     {\r
1518         strncpy(outSigningKey, device->signingKey, outSigningKeyCap);\r
1519         return true;\r
1520     }\r
1521 \r
1522     MatrixClientRequestDeviceKeys(client);\r
1523     \r
1524     if (MatrixClientFindDevice(client, deviceId, &device))\r
1525     {\r
1526         strncpy(outSigningKey, device->signingKey, outSigningKeyCap);\r
1527         return true;\r
1528     }\r
1529 \r
1530     return false;\r
1531 }\r
1532 \r
1533 // https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3keysquery\r
1534 bool\r
1535 MatrixClientRequestDeviceKeys(\r
1536     MatrixClient * client)\r
1537 {\r
1538     static char userIdEscaped[USER_ID_SIZE];\r
1539     JsonEscape(client->userId, strlen(client->userId),\r
1540         userIdEscaped, USER_ID_SIZE);\r
1541 \r
1542     static char request[KEYS_QUERY_REQUEST_SIZE];\r
1543     snprintf(request, KEYS_QUERY_REQUEST_SIZE,\r
1544         "{\"device_keys\":{\"%s\":[]}}", client->userId);\r
1545 \r
1546     static char responseBuffer[KEYS_QUERY_RESPONSE_SIZE];\r
1547     bool requestResult = MatrixHttpPost(client,\r
1548         KEYS_QUERY_URL,\r
1549         request,\r
1550         responseBuffer, KEYS_QUERY_RESPONSE_SIZE,\r
1551         true);\r
1552 \r
1553     if (! requestResult)\r
1554         return false;\r
1555 \r
1556     // query for retrieving device keys for user id\r
1557     static char query[JSON_QUERY_SIZE];\r
1558     snprintf(query, JSON_QUERY_SIZE,\r
1559         "$.device_keys.%s", userIdEscaped);\r
1560     \r
1561     const char * s;\r
1562     int slen;\r
1563     mjson_find(responseBuffer, strlen(responseBuffer),\r
1564         query, &s, &slen);\r
1565     \r
1566     // loop over keys\r
1567     \r
1568     int koff, klen, voff, vlen, vtype, off = 0;\r
1569     for (off = 0; (off = mjson_next(s, slen, off, &koff, &klen,\r
1570                                     &voff, &vlen, &vtype)) != 0; ) {\r
1571         const char * key = s + koff;\r
1572         const char * val = s + voff;\r
1573 \r
1574         // set device id, "key" is the JSON key\r
1575         MatrixDevice d;\r
1576         snprintf(d.deviceId, DEVICE_ID_SIZE,\r
1577             "%.*s", klen-2, key+1);\r
1578 \r
1579         // look for device key in value\r
1580         static char deviceKeyQuery[JSON_QUERY_SIZE];\r
1581         snprintf(deviceKeyQuery, JSON_QUERY_SIZE,\r
1582             "$.keys.curve25519:%s", d.deviceId);\r
1583         mjson_get_string(val, vlen,\r
1584             deviceKeyQuery, d.deviceKey, DEVICE_KEY_SIZE);\r
1585 \r
1586         // look for signing key in value\r
1587         static char signingKeyQuery[JSON_QUERY_SIZE];\r
1588         snprintf(signingKeyQuery, JSON_QUERY_SIZE,\r
1589             "$.keys.ed25519:%s", d.deviceId);\r
1590         mjson_get_string(val, vlen,\r
1591             signingKeyQuery, d.signingKey, SIGNING_KEY_SIZE);\r
1592 \r
1593         // add device\r
1594         if (client->numDevices < NUM_DEVICES)\r
1595         {\r
1596             bool foundDevice = false;\r
1597             for (int i = 0; i < client->numDevices; i++)\r
1598                 if (strcmp(client->devices[i].deviceId, d.deviceId) == 0)\r
1599                     foundDevice = true;\r
1600 \r
1601             if (! foundDevice) {\r
1602                 printf("new device: %s %s %s\n", d.deviceId, d.deviceKey, d.signingKey);\r
1603                 client->devices[client->numDevices] = d;\r
1604                 client->numDevices++;\r
1605             }\r
1606         }\r
1607         else\r
1608         {\r
1609             return false;\r
1610         }\r
1611     }\r
1612 \r
1613     return true;\r
1614 }\r
1615 \r
1616 bool\r
1617 MatrixClientDeleteDevice(\r
1618     MatrixClient * client)\r
1619 {\r
1620     static char deleteRequest[1024];\r
1621     snprintf(deleteRequest, 1024,\r
1622         "{\"devices\":[\"%s\"]}",\r
1623         client->deviceId);\r
1624     static char deleteResponse[1024];\r
1625     bool res = MatrixHttpPost(client, "/_matrix/client/v3/delete_devices",\r
1626         deleteRequest, deleteResponse, 1024, true);\r
1627     return res;\r
1628 }