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