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