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