Compare commits
1390 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 07c65a98be | |||
| 4fbdd70a53 | |||
| a78b260857 | |||
| cd865d21c3 | |||
| 8137c39f3a | |||
| f21946ef06 | |||
| 730b66e26c | |||
| 316b29c95f | |||
| 1e737208e5 | |||
| 36b9064e51 | |||
| 086106a96d | |||
| b9fba69dc7 | |||
| 8bad59bbe1 | |||
| 972d5cefdb | |||
| c73aeef2ed | |||
| 0f8da0b723 | |||
| a1cfb4bcf1 | |||
| f6fb082dc9 | |||
| df9f48dc38 | |||
| 0b4c94961d | |||
| 41d44f0d49 | |||
| 29255781cc | |||
| 1756aabe35 | |||
| 4ee4906536 | |||
| fbe162a1bc | |||
| cebeca85d0 | |||
| 8c23b6e7b2 | |||
| eb0e97b902 | |||
| 828e36e3af | |||
| d68a22c378 | |||
| a71970d3de | |||
| c07b060080 | |||
| b033508e9b | |||
| ba95a7bfd8 | |||
| 64d76308c6 | |||
| 52fef9f373 | |||
| c3437ac4cf | |||
| 6fd129dcfa | |||
| 1f8c67bbb2 | |||
| 82a4b53cf9 | |||
| 673db38a72 | |||
| 50d477b91c | |||
| 1c2964f3f3 | |||
| 92202e20e7 | |||
| c032609f4d | |||
| 9c54de1817 | |||
| d606bd3e9f | |||
| 7c578dccb9 | |||
| 5563213106 | |||
| 91dc87808d | |||
| 1217067f74 | |||
| 5b512f020e | |||
| 042d6bab72 | |||
| ef3ffda2d3 | |||
| 3ac6b10daa | |||
| be631849e8 | |||
| fc8b9d7de4 | |||
| 2c38c6c371 | |||
| 81e2725b6f | |||
| fc56ae3dcf | |||
| e8c56f8163 | |||
| 690c3b1977 | |||
| ac800180a7 | |||
| b7a78c865c | |||
| aeda2d363c | |||
| 69af5b3c6f | |||
| 56a7854b53 | |||
| 60d4ac14b7 | |||
| 78b9758377 | |||
| a5fd71a251 | |||
| 7958a20fc1 | |||
| 6878f52885 | |||
| ea5b4c98c1 | |||
| 60432abe0d | |||
| 2d537e5211 | |||
| 7dd086fcdc | |||
| bd6075f6b4 | |||
| 48b67759ed | |||
| f60c678af8 | |||
| a35d96c15e | |||
| ade7fabb6b | |||
| dd4cb9c793 | |||
| 2ace220366 | |||
| 4f3f9c7af8 | |||
| 43d4e7b46a | |||
| 4be02d0e99 | |||
| e34708862d | |||
| f269202ece | |||
| 408f966a50 | |||
| 553ffe97a8 | |||
| 23f16f065b | |||
| f246296ff9 | |||
| ca6faffc72 | |||
| 4c42205633 | |||
| f9881065c1 | |||
| 1af1504ef3 | |||
| 7fd1c93e1a | |||
| a33d2ad28f | |||
| bd8a97cfd7 | |||
| ce94fcc2e2 | |||
| a442ca1893 | |||
| db7746062f | |||
| bc9d5016dc | |||
| 581c02307e | |||
| a4f8c35efc | |||
| 2878148b0e | |||
| d6cfd871c4 | |||
| 18597f8f63 | |||
| 6b5f0b8ec3 | |||
| 172867fd4d | |||
| 50cc356c7b | |||
| 2ac192f307 | |||
| 03aba95e1e | |||
| 616e57eb1c | |||
| 5c695bbf8f | |||
| 861a05be69 | |||
| cf17a05ecc | |||
| 385dd9113b | |||
| 3f4f9c1fbd | |||
| bf163ef5ad | |||
| 83cf820508 | |||
| d680b331d0 | |||
| cd33d8ca38 | |||
| 9bfd88cec4 | |||
| d5fba19655 | |||
| ab29e1b319 | |||
| a2dbeda758 | |||
| 0d3fd31893 | |||
| 8b5de47acb | |||
| 5a7ea607c6 | |||
| 09fc258f9a | |||
| fdba90587a | |||
| 63ed675158 | |||
| 767e9b3cf2 | |||
| 8d4ceac21f | |||
| d1616d5654 | |||
| 492e08598c | |||
| 6716939b6d | |||
| 73959e023b | |||
| ec02d462ea | |||
| 0eee472829 | |||
| 296328e8e0 | |||
| 831090c92c | |||
| fa361aea16 | |||
| 91e9942fcd | |||
| 5eeea83c19 | |||
| 98386cf527 | |||
| a3667a21c9 | |||
| fae10ae7f3 | |||
| 1db8084c4f | |||
| b544639187 | |||
| 5e1fda9834 | |||
| 5bae1a2a4e | |||
| defe48bf3c | |||
| caf55308a5 | |||
| f2aebcb983 | |||
| 2840242331 | |||
| 69a779a7eb | |||
| b7da197846 | |||
| 0b9b01727e | |||
| 0d58695e39 | |||
| f5ba7ddc4a | |||
| 7052e9ff64 | |||
| 8796bfe874 | |||
| 9e7c6c3632 | |||
| 6617e94a9d | |||
| 7e8c8b1a14 | |||
| 9078a30e28 | |||
| 485ca402f4 | |||
| d41293879a | |||
| d5babfbb88 | |||
| b842b2f96d | |||
| 32e83a942d | |||
| d81e6a18f9 | |||
| e514415642 | |||
| d9096cc64c | |||
| f085289d05 | |||
| ef81168434 | |||
| 1f04353668 | |||
| f70b6f5ecf | |||
| ee7dc2a7ab | |||
| 1256052134 | |||
| 28eebe2043 | |||
| bb54ff7991 | |||
| aa75d02ae3 | |||
| eefef9a81b | |||
| 15646ec1d6 | |||
| 10380596cd | |||
| c4df1cb9aa | |||
| c509b6c76a | |||
| 9c2e2238ed | |||
| 2edb5d845e | |||
| 147e5f8f03 | |||
| 8e9cabcbf9 | |||
| 42af266806 | |||
| 7a1fb0b368 | |||
| 3f72b10831 | |||
| 6bd95057b6 | |||
| 859317a0f6 | |||
| ace3ff3754 | |||
| 60b627c2ca | |||
| 41615840dc | |||
| d07b389f4b | |||
| b2a35fc9cf | |||
| 900a4f848c | |||
| c4d73c9901 | |||
| eb9dd8fb0d | |||
| a134cc378d | |||
| a32fce6cdf | |||
| 9ee2d04a41 | |||
| 1ce1c5636e | |||
| 8652cdf752 | |||
| 32e2ea0288 | |||
| 7263914f67 | |||
| 76763a80fe | |||
| 72ae9dd885 | |||
| 816e722807 | |||
| 1a1fe97d00 | |||
| 16687f24f9 | |||
| ea41076c82 | |||
| 130dc58a5d | |||
| 9d6e192b9f | |||
| 82d1d64f85 | |||
| 39a4dc911f | |||
| a7ed8e0b45 | |||
| e64ab6cf9c | |||
| 43d883da9c | |||
| 42dce635de | |||
| 7794b230e1 | |||
| 2d56f550aa | |||
| b39a224be8 | |||
| e6bf74b7db | |||
| 5f06dc8229 | |||
| 46d8d26b71 | |||
| e23be44345 | |||
| 57fb659b86 | |||
| 1933fe7a8f | |||
| 6a6d94a065 | |||
| ccfb66c576 | |||
| c2d3afffff | |||
| e1f6fd8a1e | |||
| 2f377d536a | |||
| 26789f22b0 | |||
| 3aa1c30f5c | |||
| 5e23bd09bc | |||
| c8bad4b86e | |||
| 07c366983d | |||
| 51abedda59 | |||
| 9d5a1fda3c | |||
| 61ea15eb39 | |||
| fe69948926 | |||
| 248b8db309 | |||
| ca5cabb7e1 | |||
| 7a2b1e6e1f | |||
| c0f84bb8da | |||
| 9a9b142630 | |||
| a2dcfa905f | |||
| 1e1e0d7e6d | |||
| 5d1a738162 | |||
| e4e9efb7d9 | |||
| b1074e400e | |||
| 9574f93320 | |||
| c3bcbf8952 | |||
| 739dd8cf93 | |||
| c7e1d617ec | |||
| ae85e18588 | |||
| 516d849ef2 | |||
| eb2bf61236 | |||
| 0985043a6a | |||
| 36b739b830 | |||
| eeea4f23bc | |||
| c9a6898c73 | |||
| 18c33aefa1 | |||
| 2c7c251e47 | |||
| f3ee6c735b | |||
| 4f9cb48ef8 | |||
| 9880619d81 | |||
| a8204987be | |||
| cc585974e0 | |||
| aebd5fe4eb | |||
| 7ab134233e | |||
| 0f54877b31 | |||
| 1c9aa7c907 | |||
| 4041c875d1 | |||
| d226738d15 | |||
| 404b95ab84 | |||
| 0d082f6b96 | |||
| 5bf9182549 | |||
| 1177bdd394 | |||
| 98ca774118 | |||
| 8f13023333 | |||
| 51b3bf4c75 | |||
| 283137f190 | |||
| 33b348c376 | |||
| 7e7c91699f | |||
| b4b111f91f | |||
| 0f7d839c49 | |||
| ee6c0d9486 | |||
| 7121179b6a | |||
| 59edc22a35 | |||
| e9058f58be | |||
| 7dee9648cf | |||
| 786e9b5db9 | |||
| e20db574ec | |||
| c6c259144a | |||
| 58e99a645f | |||
| 5d6cf2d33a | |||
| 5f1a22e26f | |||
| 6afb6f1e27 | |||
| 032356566e | |||
| d7468ec158 | |||
| 929538608a | |||
| d5a56bcbc7 | |||
| 84ff8980f3 | |||
| 84d83dda0a | |||
| fb4b347433 | |||
| e1c6ac9787 | |||
| 35bb18a1e8 | |||
| 483bef0748 | |||
| 3c25ad1489 | |||
| 8d83a996a6 | |||
| 595e34aad5 | |||
| cc35ee72b8 | |||
| b148ea2fb1 | |||
| 2bb66f0f2e | |||
| 8057a3691f | |||
| 057cf0bde7 | |||
| c43d9d3e43 | |||
| 331ea35be6 | |||
| fc9dbae23b | |||
| 9733d9842a | |||
| b9fd451d54 | |||
| 3511a4a053 | |||
| 7f52292e92 | |||
| 980c9cb4f5 | |||
| 2368ac74de | |||
| cc69efc3f8 | |||
| 5a06c11ac0 | |||
| d2fdc20733 | |||
| 16871d1dcd | |||
| 508f1bc976 | |||
| 0419967158 | |||
| da5c79482b | |||
| 4996a19b31 | |||
| d720861fbd | |||
| c517f38ec8 | |||
| ac863409bb | |||
| 417008cc87 | |||
| 5dd404d2f1 | |||
| 2c0466a8cc | |||
| aec65c818b | |||
| 0d9d130833 | |||
| 26e259455a | |||
| 2f637cda6f | |||
| 64ae77ec70 | |||
| 932cf2ad99 | |||
| 68f8ed5a92 | |||
| 4670142a83 | |||
| 9a53602d73 | |||
| 48bf20fcca | |||
| be6c7c8b4c | |||
| 832146b43d | |||
| 66d4ced90f | |||
| 5c2915bb6a | |||
| fb23cbff97 | |||
| 9fe880d05d | |||
| 7af2bea2fc | |||
| f1829faa99 | |||
| a767187ff6 | |||
| 7369010964 | |||
| 07f6a3b345 | |||
| 8f6f20bbe7 | |||
| 6a9cf1f9ce | |||
| f8ce49cc49 | |||
| 46d71acb44 | |||
| 9f0563eb95 | |||
| bd48cae28d | |||
| 69749ab8fe | |||
| 97b5a3a61b | |||
| 0ec3aca727 | |||
| 18e6f10367 | |||
| d77efd7327 | |||
| 5b28c69cfc | |||
| 61796a2725 | |||
| f538b5e595 | |||
| 4d1363e683 | |||
| 9324702b04 | |||
| 0c4c73084d | |||
| 6ce51b5890 | |||
| 3ff1844da4 | |||
| 99cbf3122b | |||
| e09ec389d1 | |||
| 2aec3d0502 | |||
| 2f974c11f7 | |||
| 79ee4b59ee | |||
| 01a66d4874 | |||
| 1ddcc97f84 | |||
| c6a12304ba | |||
| 50114f4e74 | |||
| 792b60c501 | |||
| d8465a2c1e | |||
| 8550eaeed0 | |||
| 071ba2376e | |||
| 7ec98ff721 | |||
| f03f7c8d1a | |||
| 0dd2b12141 | |||
| d513cb9a47 | |||
| e550c16c14 | |||
| 0da29057f8 | |||
| 5881503a6a | |||
| e261d37756 | |||
| a2da63758c | |||
| a86754a317 | |||
| 5c3d02cb89 | |||
| e84a43dbaa | |||
| 2fe08a90c5 | |||
| bdb9d274ce | |||
| 862b4a3df7 | |||
| 1dccea8735 | |||
| e1fe1279c2 | |||
| 15513b0ada | |||
| c9e6d3e2dc | |||
| 400879c819 | |||
| aac9d9c29b | |||
| 770a67678c | |||
| 1452a2124f | |||
| daa3b80870 | |||
| ccd9ad7b49 | |||
| 4212980f32 | |||
| 9b1c5079e6 | |||
| a341caa773 | |||
| b4bf544676 | |||
| bfb55d782f | |||
| 3bad2a84ad | |||
| 866c91ffbc | |||
| c03ba55f24 | |||
| 1f5c4feafc | |||
| 6d2055449d | |||
| f5cc6feccd | |||
| c5d5deba8e | |||
| d829fd3376 | |||
| 50a248c18b | |||
| c366eadf33 | |||
| 318ede131d | |||
| 146db59370 | |||
| 97dd68e7fc | |||
| 4e4c745032 | |||
| f03767318f | |||
| cf83112bf0 | |||
| 575eea4701 | |||
| 5377bb7ecf | |||
| 629b74e4f5 | |||
| 9475bb4d53 | |||
| 452588a18e | |||
| ff5dedbb07 | |||
| 6539dc3060 | |||
| 7b8608e4c5 | |||
| 5c3972938e | |||
| b936d3e32d | |||
| da8d74ccc1 | |||
| 6aad2a661d | |||
| 3139204f4b | |||
| 1684dbe0a1 | |||
| 9531dc195a | |||
| abab6dcf0f | |||
| d699161981 | |||
| 927c44bec3 | |||
| 96248248ca | |||
| b7b86fa95b | |||
| 4f7186a840 | |||
| 9b062fa782 | |||
| 4d8c3172a4 | |||
| b1c9ea9aa3 | |||
| 49eee14665 | |||
| 6cec946401 | |||
| d06e8ccfc5 | |||
| ac1d5afac3 | |||
| f51b51b19e | |||
| 16c9845a47 | |||
| 4dbd1a7db4 | |||
| e5c85410a8 | |||
| 98ea3f096b | |||
| f0986643ce | |||
| f4d4d96c8e | |||
| b214a0e098 | |||
| 70380a6ee4 | |||
| 39997376af | |||
| 58fa9d7767 | |||
| ad18ecc2c1 | |||
| ff500fc707 | |||
| 76a286c91d | |||
| 6cc31a0e13 | |||
| 228b963f74 | |||
| 1783cd7738 | |||
| 32e8cb76b0 | |||
| 81c9f57e02 | |||
| e162c99074 | |||
| 6465398322 | |||
| 6b948711b9 | |||
| f803e58e36 | |||
| 23b3e3aa10 | |||
| fcfdf5c57c | |||
| 3b01e4f9a6 | |||
| 3c44f87bee | |||
| af6cd85cf4 | |||
| 54654aa0c7 | |||
| 778d1211c7 | |||
| c69d28ef79 | |||
| d0c8ec7a22 | |||
| 01e3a31a11 | |||
| 83dcb0eb48 | |||
| 6c92bf8745 | |||
| ba6fc794b0 | |||
| 17b86b7c38 | |||
| 9894d3c72d | |||
| d5154577a0 | |||
| 5be00d5950 | |||
| f3928e8d35 | |||
| c766d8b3ef | |||
| 4d33fe1c31 | |||
| e80713e933 | |||
| 5d356ca1b5 | |||
| 56daa6cb8f | |||
| bcc04bdf35 | |||
| ebe8ce3177 | |||
| 1d02515186 | |||
| 0abef77408 | |||
| fe7f157253 | |||
| 5a62ec3fc1 | |||
| 5727726e5d | |||
| dec4b2122b | |||
| cfcafc8425 | |||
| 42934ea7e4 | |||
| 7e64c15145 | |||
| e56d380f65 | |||
| f72d9c2016 | |||
| 95f1867816 | |||
| b1062a67e0 | |||
| 02a213c1b5 | |||
| 64ec5ec561 | |||
| 953d4c0ef7 | |||
| 1469e9033c | |||
| 260e7c2d5e | |||
| ae4ed2e7c8 | |||
| 28fadb875a | |||
| 3dbad4229a | |||
| c497efc27a | |||
| 0154cccd4b | |||
| 99232f2340 | |||
| 592be5bd57 | |||
| dc0fe35103 | |||
| 7accfdc2ef | |||
| 655d7d00d0 | |||
| ac5b439249 | |||
| f76bc16a10 | |||
| a89a12701a | |||
| 843fe6a239 | |||
| cb06feea22 | |||
| b0e7f3d031 | |||
| b96532878b | |||
| b439d60b1f | |||
| e8af3e2d60 | |||
| 1f3b8048c7 | |||
| 48257ac7cd | |||
| dffc65007c | |||
| 3796469a29 | |||
| d49ceddfd5 | |||
| 03c3f66c9a | |||
| 7b06822687 | |||
| 1f128c1dd6 | |||
| e92841d6bc | |||
| a112076664 | |||
| 2f9106a6d0 | |||
| 8a70df80b2 | |||
| cbcfd5d531 | |||
| b451460d8d | |||
| 772a94cc8c | |||
| 0f077ffcd3 | |||
| 9eefe9377e | |||
| b35f2669d2 | |||
| 4102d25ebe | |||
| 177fa140fc | |||
| e0eeba21ff | |||
| e4f94f3174 | |||
| c2f529ed71 | |||
| 0b91ec29a4 | |||
| 362a394a11 | |||
| 6ddb22d6ab | |||
| a3f289c6fc | |||
| 44d8f99bc4 | |||
| eb3fb6986d | |||
| b6fb25fdfa | |||
| 679d3084aa | |||
| 94b60f915f | |||
| 3bf1060705 | |||
| d600af7c0e | |||
| 9649100adb | |||
| 292a9831f5 | |||
| 04d8348477 | |||
| 7049bd9183 | |||
| 1c7ffedf1a | |||
| 7cc06cb76d | |||
| 9479f23f4b | |||
| c719d6d22e | |||
| 6aa766efd0 | |||
| a0807cb337 | |||
| 480800b996 | |||
| 1c0288e42b | |||
| 0a97ac8316 | |||
| 46006cf108 | |||
| 10adb6d26b | |||
| 5abab932db | |||
| abef9fae1a | |||
| b05d6874c0 | |||
| 3f2b12c996 | |||
| 838488702f | |||
| 6c62b5f637 | |||
| 826dc46ddf | |||
| d9e455ab82 | |||
| 19b8ab409d | |||
| 99b61ef660 | |||
| fb467b75e0 | |||
| 9e2fa13b9c | |||
| 38e2d0bdcd | |||
| e8d5d29d2c | |||
| 88376e85cd | |||
| b1bfb868ab | |||
| 6b3a550b22 | |||
| d9f9ed9d68 | |||
| 188aaecde3 | |||
| d41753ea17 | |||
| e16c113db0 | |||
| 3f8590f86d | |||
| a0bd88aefc | |||
| 74056de8d7 | |||
| 4f14728c91 | |||
| 57b810d70c | |||
| b3fedda90c | |||
| 20e082d416 | |||
| 9e0ff4b9a9 | |||
| b57bd55204 | |||
| 4cad934a6b | |||
| aa2b2dfff4 | |||
| 6c2a9dae55 | |||
| 360864841e | |||
| 563ba884eb | |||
| 13ffadc32a | |||
| e246735866 | |||
| 6593f61556 | |||
| 423d6ace83 | |||
| ce315a5229 | |||
| ca1163c9cc | |||
| e576af8267 | |||
| 118497f9ec | |||
| ac06172cf2 | |||
| 2654e909be | |||
| a1b1862479 | |||
| 5001cef372 | |||
| a6f74fa22c | |||
| 56cfa8f4f9 | |||
| 8d642784bb | |||
| e45bf9f9bd | |||
| 3a516a5fce | |||
| 9fc7d1c78b | |||
| c6f0e47077 | |||
| 96c796bb0e | |||
| 63fa645cea | |||
| 0d4fac5962 | |||
| 2e497198f8 | |||
| 9707d73ead | |||
| 5ae84a9a80 | |||
| 57c3224b6b | |||
| 4a5a0293a9 | |||
| 3f16b324d7 | |||
| a5bfab1d55 | |||
| e430f65ce8 | |||
| 441626bf68 | |||
| 02e51a9cb9 | |||
| 4492df6fc6 | |||
| 573b672470 | |||
| fe3c73602f | |||
| fd2dc18746 | |||
| 55549f61be | |||
| cf359dede5 | |||
| 13f50fbf6c | |||
| 0f7646ade1 | |||
| 4e1fe3bec4 | |||
| c6d99305d5 | |||
| c86ebaddd2 | |||
| 048d6a774c | |||
| 43f2f60bfd | |||
| 12cbff2688 | |||
| bdab40a772 | |||
| 87de9c7453 | |||
| 765959758b | |||
| efdeba79f9 | |||
| e25061f8cb | |||
| 345f4f39f4 | |||
| e12b9e3027 | |||
| a22529344a | |||
| 64b7c4eb59 | |||
| 1988d1e9a3 | |||
| efc2397267 | |||
| ecb521205f | |||
| 7db40746bb | |||
| 3639215d53 | |||
| d51e9694f6 | |||
| ae21a56be2 | |||
| c252b984e0 | |||
| 300fdec83f | |||
| ee9d47f647 | |||
| 2e80073f28 | |||
| c3d5932a0f | |||
| 7fdccfcea4 | |||
| 630836b3c7 | |||
| 3705925a22 | |||
| b7a3ab7c5b | |||
| 5692480112 | |||
| 5b63bd4cf1 | |||
| 9711e1b2f5 | |||
| a17346158f | |||
| 30ddbab686 | |||
| b436918cb3 | |||
| b20424b8b5 | |||
| 5f933b1033 | |||
| 5e8224f19c | |||
| e68134dd53 | |||
| 945c16a7fb | |||
| fc8cd2e7e5 | |||
| 4115975135 | |||
| 35a26be7b0 | |||
| 052832b4d3 | |||
| e516e15d2b | |||
| 2bc9407563 | |||
| 47f8b6cb79 | |||
| e11dd099d8 | |||
| c294a376e8 | |||
| ffa0190517 | |||
| 3a863fa8fb | |||
| 75d396e2ec | |||
| 672a640854 | |||
| 40010cd511 | |||
| a6d33fcd16 | |||
| d08606e3b9 | |||
| ae8a8c6cc3 | |||
| 0d87d0f786 | |||
| a9ba2dd546 | |||
| 00638ba74c | |||
| 6a7b28c085 | |||
| 1bcd8c7aae | |||
| ce973b35e9 | |||
| 8a1b1eccd6 | |||
| e068b4987e | |||
| 3a1eb62c38 | |||
| c8021cf2ba | |||
| a48fd77c4a | |||
| ab0e27622e | |||
| 7b044ef5dd | |||
| 8a9b72ae6c | |||
| e9cef35f99 | |||
| 3a183b4c22 | |||
| 5dbee7dcdc | |||
| b9b00743c9 | |||
| 14be1f888a | |||
| a77295120a | |||
| a6ad0e35b3 | |||
| 5b3ec33ddc | |||
| aa10e70851 | |||
| 826b2874ec | |||
| 8f17b6c38d | |||
| 9ae9c98340 | |||
| f6da282557 | |||
| 9ad34e8565 | |||
| a973c372e6 | |||
| 00e93e08e7 | |||
| 75a2d2d92c | |||
| e066346b16 | |||
| 754453c135 | |||
| c481caf060 | |||
| 6d868908cb | |||
| d2678e2f01 | |||
| c24316e5e3 | |||
| 48bc5b9b7e | |||
| 01c828ed4b | |||
| 102994b030 | |||
| 38dfa90441 | |||
| 274bc20603 | |||
| c8d561d17d | |||
| 8ea2cef55d | |||
| 4086492ef9 | |||
| 6bfa3df691 | |||
| f4607146e2 | |||
| 0a7193de62 | |||
| 5c2e6b2743 | |||
| 47256eb4f9 | |||
| 1c8d57321c | |||
| 322aa776f2 | |||
| 517b71cb06 | |||
| 17a9c4829d | |||
| 59a5805af7 | |||
| 882f3cad25 | |||
| 5ce3c45637 | |||
| ab617ddda0 | |||
| 3a1c5580e7 | |||
| 8486976804 | |||
| 8f13bb5ca5 | |||
| 7257cff5fe | |||
| eb47869a45 | |||
| 636f4f8f8d | |||
| 819f962419 | |||
| 3c9e84397a | |||
| f7bf6ba220 | |||
| 539cbee087 | |||
| d442cac936 | |||
| 72ed024303 | |||
| 148c4907b5 | |||
| 8c91314b58 | |||
| 9006bd1bba | |||
| 32e7209c66 | |||
| 4dfa41ce8f | |||
| 1a5ead58f6 | |||
| 220617fd04 | |||
| a37ce68eae | |||
| 0c4ca84eec | |||
| 77b7451f59 | |||
| bac105a9aa | |||
| 832bba8a2b | |||
| 0e39a7cbf2 | |||
| db11940f8f | |||
| 4ef654f1d1 | |||
| 793e18e44f | |||
| 578b446ea4 | |||
| 3fe5f13df4 | |||
| 54f00dc05a | |||
| 464e40bc32 | |||
| f22660d5e9 | |||
| 2db1bc75c1 | |||
| 18ec101719 | |||
| a762b2027f | |||
| 934181be44 | |||
| 3d34743ae9 | |||
| 718864bfdd | |||
| 24ac5b9f16 | |||
| cc835da347 | |||
| 933eec2d1d | |||
| de4a8e3e7a | |||
| b08c740181 | |||
| 950bb83f8d | |||
| a06aa43dd1 | |||
| a572f61edf | |||
| a964585e35 | |||
| 71407a636c | |||
| b7a8a93a73 | |||
| 600fd62dc5 | |||
| 00d1437e5a | |||
| f774159e15 | |||
| a1fba86f75 | |||
| 3c4d1cfb47 | |||
| 3ab95c74c7 | |||
| 8c1a81eebc | |||
| acfa8a4190 | |||
| 0771d73543 | |||
| 92546acc85 | |||
| c7f258d8b0 | |||
| 1e2fd02478 | |||
| b0c70dc606 | |||
| 0fea33de4d | |||
| 053b609899 | |||
| a54a421f91 | |||
| 78147e0d33 | |||
| 430c573494 | |||
| 9db09e22b2 | |||
| 94fadf74b9 | |||
| 7c01f8490f | |||
| ca00112804 | |||
| 6c0ef2db51 | |||
| 938a03867b | |||
| 90cce1a47c | |||
| c427caf473 | |||
| 3ad675db5c | |||
| efde81646b | |||
| 8f9859c41c | |||
| 6248e2bc68 | |||
| c0ccd55df8 | |||
| b125b2e9da | |||
| 3c838b2f4a | |||
| f4fba3efb7 | |||
| e64b2f65da | |||
| 9e12a88e03 | |||
| 0af38d9a76 | |||
| ade3a09848 | |||
| 3641e0d540 | |||
| e1d8c72d3a | |||
| c8d9581f8b | |||
| 8cc632ffd1 | |||
| 1b7f4b4b42 | |||
| 928871d16e | |||
| 87f5b251f5 | |||
| e76272a68b | |||
| b30c74cb01 | |||
| 686541155f | |||
| b256b6f625 | |||
| 7b913a0759 | |||
| 9e60a9dd3f | |||
| 00bdb08973 | |||
| ed70ccb988 | |||
| e52af8a315 | |||
| b5450ee5ad | |||
| b105b34cf6 | |||
| e0b0b632ec | |||
| 4febe45364 | |||
| 70de11a73a | |||
| 58f2d53293 | |||
| 6540b9bb49 | |||
| 6c53842599 | |||
| 663d043abb | |||
| 386cbce344 | |||
| ece9252a3d | |||
| c13cb4957e | |||
| 1f34a016e5 | |||
| b8dd704658 | |||
| 273e96265f | |||
| 0e3ea58aa4 | |||
| ba4e304cad | |||
| 723e3a3135 | |||
| 1c754e663d | |||
| 846844e67b | |||
| 51d96213ba | |||
| 80868919ae | |||
| e3e9898f88 | |||
| f2055f8069 | |||
| 151f87a417 | |||
| 0e4f4d69b8 | |||
| 8db84a3c9e | |||
| 1b18402707 | |||
| 6ec8ff6363 | |||
| 906c09987f | |||
| c93a9ef9a3 | |||
| 51f2e773a0 | |||
| 3c6c5f4fae | |||
| a8d49b86df | |||
| 2949dcc773 | |||
| b790bf9397 | |||
| 91941a5360 | |||
| fabf0d40bc | |||
| 08bec243ff | |||
| 60d82fde38 | |||
| 866158f6ff | |||
| f883293168 | |||
| c6e883da68 | |||
| cdfb51a606 | |||
| 3e88905518 | |||
| 156c351023 | |||
| a98c1fd00d | |||
| f4bfbdf97d | |||
| 2e30e11101 | |||
| 3af6ba245c | |||
| db1d31c9cc | |||
| 444a82fd9e | |||
| 6e218bdebc | |||
| 9b3bf5a4fa | |||
| 237edcd747 | |||
| 271e925adc | |||
| 514530c19a | |||
| 58aec0d126 | |||
| 63c8696cac | |||
| 6aff4fc2c0 | |||
| 8375c47f42 | |||
| 1b2387bcb2 | |||
| 1e71036b19 | |||
| e0f5dc97a5 | |||
| 64f367d474 | |||
| bb39c2aa9d | |||
| b82cd7fa5a | |||
| 6780dc0312 | |||
| 72519b7386 | |||
| c7b41e3fca | |||
| 5852d751bd | |||
| cda596ebed | |||
| a5e17be3a1 | |||
| 33cdc108b5 | |||
| c3dfd2e744 | |||
| 8ab5bb7fca | |||
| 80d8a8f41f | |||
| 46fe998a33 | |||
| 7efbba5d8b | |||
| 907d50f773 | |||
| 9cba98ae1b | |||
| 108950c706 | |||
| 4452e15489 | |||
| b026d90bcf | |||
| aa5f74ffb1 | |||
| 469c6cf104 | |||
| 3c5b005aae | |||
| eef0bf4b91 | |||
| ad1011ec53 | |||
| c215ac1f4b | |||
| 187696aa62 | |||
| e1e094f2f4 | |||
| e2d4be2050 | |||
| 7dc4d47d3c | |||
| 9bad6faedd | |||
| 3a715ea822 | |||
| f58f3fe055 | |||
| 6eda742fff | |||
| 14f451c6dd | |||
| 562087a70e | |||
| e3e0eb9144 | |||
| b6d48cb31a | |||
| e4257f9aff | |||
| 6b871b0a48 | |||
| 561fb97a7b | |||
| a5080b6ed9 | |||
| 8034ac20f9 | |||
| f5c0ea4605 | |||
| ddf448aa02 | |||
| b960c372ac | |||
| 05e4bc0557 | |||
| d79c70177b | |||
| db7eafb7d0 | |||
| 9239470c1c | |||
| dab20638f9 | |||
| 273d0a0edf | |||
| 1c12b23e4c | |||
| 37bea19ab5 | |||
| eb0c3449fa | |||
| b3f146c932 | |||
| 34d458d3a3 | |||
| 1c55565403 | |||
| c8da05125e | |||
| 9ec9a59509 | |||
| 4f9452c9b0 | |||
| dfed2f1b3a | |||
| 5850ff4cfc | |||
| e64a8113e9 | |||
| 2a4b83b5e6 | |||
| 799447f481 | |||
| 1a0b90291c | |||
| 9a058b9ea0 | |||
| 1ef645d285 | |||
| 57e09c07dc | |||
| 1e4c16b9a9 | |||
| 26486fc905 | |||
| c4884887ef | |||
| ddde87f14b | |||
| 3e1fddccfd | |||
| c1949e3fc6 | |||
| 63bc004e26 | |||
| e99341df69 | |||
| 7667f0c429 | |||
| d0609822bc | |||
| 262fe5630f | |||
| 2176b7ee39 | |||
| 2bc738be8c | |||
| f5b59c3de2 | |||
| 31e5d4d663 | |||
| 6a386ca5fb | |||
| a18919bf50 | |||
| f92153757b | |||
| 83d0abc6d7 | |||
| f919ee3b7d | |||
| 0bfa63b31e | |||
| 229e6b28a5 | |||
| fee9d52dde | |||
| d4890807f9 | |||
| 58f92e59fe | |||
| 1f38becdd9 | |||
| 2d9e8170fe | |||
| aeb419207a | |||
| ee713d42ae | |||
| 97578e418c | |||
| 7d69fb2314 | |||
| 32cc477f54 | |||
| 457c5f4fa3 | |||
| c604e59dcc | |||
| 026659ef68 | |||
| 195ade911e | |||
| a881dad529 | |||
| 090b4495ea | |||
| 3dfc60506f | |||
| d4b67de9c3 | |||
| 78e621010d | |||
| 1b0849a20c | |||
| 32bf3143c7 | |||
| 3d22b6d5a4 | |||
| bc29bcef38 | |||
| 859b12fca9 | |||
| c564696a8a | |||
| d3ff402813 | |||
| 08951e1c56 | |||
| afda63f9e2 | |||
| d7e6dd22e6 | |||
| 68a6d2707f | |||
| 6666cd7a79 | |||
| 75bb44bebf | |||
| 0504240878 | |||
| c0910a3693 | |||
| 183d4595aa | |||
| 580861cd1c | |||
| 1f761f3c13 | |||
| 5dc6416a76 | |||
| c808a72914 | |||
| 6a1edf133d | |||
| d0f0b650bd | |||
| d027005e87 | |||
| 43f0ba7711 | |||
| 8ce216974f | |||
| f7d4129607 | |||
| 1107f27c3d | |||
| 3113f6698f | |||
| 38c38bc9f0 | |||
| de71d7e434 | |||
| 57a1743566 | |||
| 1025d42624 | |||
| fcb37b6962 | |||
| 2ab697328f | |||
| eb20abe7b8 | |||
| fa71122e7d | |||
| 785a3349ab | |||
| 6329dbe6c2 | |||
| ec5306978e | |||
| 6b363120ef | |||
| 4bafb3818b | |||
| 317965995a | |||
| 74e9209e95 | |||
| da76a2700d | |||
| ac33ca5fa0 | |||
| 2bc0ac8fd7 | |||
| 5a108f53cc | |||
| 1f2cdfc601 | |||
| 02165f7a05 | |||
| 98600e4c1e | |||
| bb6145d581 | |||
| 975626a4f8 | |||
| c3aa03e486 | |||
| 51798e90bd | |||
| 5fa6f34e41 | |||
| f9d2d32337 | |||
| 2dd2763d39 | |||
| bb6cf83a39 | |||
| a7dd690189 | |||
| e060606331 | |||
| a30e40ed3a | |||
| e59acfe28c | |||
| d312aaecec | |||
| 2528a5501b | |||
| cf241d8c32 | |||
| db5d34e385 | |||
| 7d2865f004 | |||
| 826705c174 | |||
| 5a1e347333 | |||
| 882b206144 | |||
| 8d89037296 | |||
| deb8ef801b | |||
| 0a45e401e3 | |||
| 456e00e953 | |||
| ab846f79f1 | |||
| b1c2da9a68 | |||
| 1ab33a28c9 | |||
| a3a9858bf4 | |||
| 6951f7f5bf | |||
| fb8045b254 | |||
| be7c3239a8 | |||
| 956e270941 | |||
| 65721aafb9 | |||
| 9d150e5cc6 | |||
| 43dd4452cd | |||
| 4abb08c4a1 | |||
| 544da0d324 | |||
| 29aa9a78c3 | |||
| 4dc0e0ef3c | |||
| 680ef6b93a | |||
| 078855b4c9 | |||
| 0d80c0e3fe | |||
| 0901243440 | |||
| fc76cc443e | |||
| 45cf32dfab | |||
| aa4dbe4bc6 | |||
| b550e9ba14 | |||
| 79db05cffb | |||
| e45c57d8fe | |||
| 2eefb3a090 | |||
| 6754defb9b | |||
| 078a75ea27 | |||
| 0d74189cde | |||
| 196dfaea03 | |||
| f6496d01c7 | |||
| ca515997f1 | |||
| 3fe63c328f | |||
| 26d690a21c | |||
| 045d6647b2 | |||
| 7f54e4592f | |||
| 4899f98f7f | |||
| 93be96c85c | |||
| c014f980cb | |||
| 5601435449 | |||
| 5b919fc9df | |||
| d57666c0b8 | |||
| ce966ed6ce | |||
| 3a6d048b9f | |||
| cf779822fe | |||
| c8dd5c42e9 | |||
| 128c74ace5 | |||
| 85ea9554e5 | |||
| 5b25b8967c | |||
| 3b755fc15e | |||
| da61005ee4 | |||
| 7a263433c7 | |||
| ee27c19bf7 | |||
| 6d21df6d29 | |||
| e989bc2377 | |||
| 28b44a7f03 | |||
| 265ac1f97b | |||
| d044565caa | |||
| aa7d225867 | |||
| af2de2d8ef | |||
| a28a664302 | |||
| c1423e9326 | |||
| 8bfc186595 | |||
| 0cf5356f15 | |||
| c03940e6d5 | |||
| f93170323b | |||
| a3cdd31713 | |||
| fb89de8267 | |||
| 223e65fc26 | |||
| b5b2eafbec | |||
| 27b0ea1aa0 | |||
| 4c4b1f3abc | |||
| 99e621a82b | |||
| 4eedbfbc11 | |||
| 898265b257 | |||
| 9d400a7494 | |||
| a4ca6dbf38 | |||
| b23d30c951 | |||
| bb96ab89f8 | |||
| 4cd8a8400f | |||
| 28d4a69552 | |||
| a6ca114bac | |||
| 6497d6d676 | |||
| da93cf7dc4 | |||
| 2b4f4b17c3 | |||
| 0f104c7433 | |||
| 726e4b9aa0 | |||
| f227f583e0 | |||
| 36444cd3a0 | |||
| 9c489b398d | |||
| ce03f016b9 | |||
| 4557494da6 | |||
| 093856671a | |||
| 471ac07c88 | |||
| f4f3ca4b25 | |||
| c009f54ba9 | |||
| 546dc9a652 | |||
| 717f86332f | |||
| 6c975b01b5 | |||
| 182733e984 | |||
| e4964b92a2 | |||
| bdf460cd91 | |||
| 0276552c72 | |||
| 3441c6cf9a | |||
| a07f89e340 | |||
| cacb20e3ef | |||
| 255d3b7a18 | |||
| 6ad7d29364 | |||
| 333c4f0644 | |||
| 6ce23b17a6 | |||
| 0cb34f7c86 | |||
| 0d84cf7b74 | |||
| f2ea72224d | |||
| 01cc896dab | |||
| 278c6059ae | |||
| d164165c7c | |||
| 5a9b75c3ff | |||
| c7c63fa41c | |||
| 0e59078211 | |||
| 120d5edb03 | |||
| afc33e616c | |||
| 324411201e | |||
| e1ecc9de0d | |||
| c9c4473cd4 | |||
| 41be4c5209 | |||
| 426f60a6a2 | |||
| eddf202a76 | |||
| d4cd8710a4 | |||
| a443e7277d | |||
| 2232092f11 | |||
| 2547d6ed64 | |||
| 6cb37520ce | |||
| 50a5ec89e9 | |||
| 285dc129c3 | |||
| a59fdc08bb | |||
| 9de98dfa65 | |||
| c03c90c1cf | |||
| 4a6208f808 | |||
| 1aa48beca9 | |||
| e05444554d | |||
| 411095425c | |||
| 6b9075aa42 | |||
| 01dc166293 | |||
| 584a82b19b | |||
| 0aba22855b | |||
| a50743874f | |||
| 7851eefb61 | |||
| 8470b494f4 | |||
| c92d946777 | |||
| 0cfc7540cf | |||
| 6990c1ca5c | |||
| 0ee63344da | |||
| 3daaa457e1 | |||
| 7a826fb7d6 | |||
| 95dfedb70a | |||
| eb58dcc556 | |||
| 57e9b36fac | |||
| 90865e2a0a | |||
| 52d96ceb60 | |||
| 8a720af215 | |||
| bac95f4494 | |||
| 31293c6c2b | |||
| 089f92d238 | |||
| 1a466eb667 | |||
| 8bbdd28a8b | |||
| 61452ad190 | |||
| 4816d93e96 | |||
| f25af209e1 | |||
| e6891addfb | |||
| f57b7782f4 | |||
| e91cee7154 | |||
| 5b46fa73e1 | |||
| b309441dec | |||
| 391efcec12 | |||
| 4e583bcb8a | |||
| 5e621b7132 | |||
| ab796cb32c | |||
| dd82e0b8fc | |||
| 7125730917 | |||
| db2771bd17 | |||
| 95face4aa4 | |||
| a7cb80df1b | |||
| e7d5b44343 | |||
| 47478702b2 | |||
| 648895e2b0 | |||
| c8bddea6e1 | |||
| f42215180e | |||
| 1e77e91ec4 | |||
| 95613be6c9 | |||
| b83e6be01e | |||
| 086b6127e2 | |||
| 54fd40d8f5 | |||
| 67d968d4fa | |||
| 7e14857239 | |||
| 2425cd99cf | |||
| 7c97c27441 | |||
| cbadd90eff | |||
| d18b57dcdd | |||
| 23c5929cfa | |||
| 7bfa622a57 | |||
| f4e0cab11f | |||
| f07735291e | |||
| d4e7076e1d | |||
| 61e95abaf2 | |||
| 8763a2468c | |||
| 8f6e3033e3 | |||
| 2f94886663 | |||
| 8b728d4ada | |||
| 26faf0d4c0 | |||
| a3113bd125 | |||
| a75d7171f8 | |||
| c3eaa864dd | |||
| 6a561c20e2 | |||
| 0581b8a884 | |||
| bdfdaeb0ae | |||
| c24c20f7f1 | |||
| 6b3acf1bf7 | |||
| 822cdae8f3 | |||
| 4fcbb61145 | |||
| 4628481d0e | |||
| e05de8d4ce | |||
| 6bc79b04cf | |||
| fa88751548 | |||
| 09c906b1d7 | |||
| 6fab6ef318 | |||
| d5728f235d | |||
| 6827c70886 | |||
| ab1a6a6b37 | |||
| 3c9c7290ae | |||
| 530f8057cc | |||
| 8e0e752f12 | |||
| b1493c5217 | |||
| 7988dc6cf3 | |||
| 180796c747 |
@@ -10,6 +10,7 @@ target-applies-to-host = false
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
uniffi-bindgen = "run --package uniffi-bindgen --"
|
||||
|
||||
[doc.extern-map.registries]
|
||||
crates-io = "https://docs.rs/"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- description of the changes in this PR -->
|
||||
|
||||
- [ ] Public API changes documented in changelogs (optional)
|
||||
|
||||
<!-- Sign-off, if not part of the commits -->
|
||||
<!-- See CONTRIBUTING.md if you don't know what this is -->
|
||||
Signed-off-by:
|
||||
@@ -15,12 +15,9 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Run Benchmarks
|
||||
run: cargo bench | tee benchmark-output.txt
|
||||
|
||||
@@ -12,12 +12,78 @@ on:
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
MATRIX_SDK_CRYPTO_NODEJS_PATH: bindings/matrix-sdk-crypto-nodejs
|
||||
MATRIX_SDK_CRYPTO_JS_PATH: bindings/matrix-sdk-crypto-js
|
||||
|
||||
jobs:
|
||||
xtask:
|
||||
uses: ./.github/workflows/xtask.yml
|
||||
|
||||
test-uniffi-codegen:
|
||||
name: Test UniFFI bindings generation
|
||||
needs: xtask
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Build library & generate bindings
|
||||
run: target/debug/xtask ci bindings
|
||||
|
||||
lint-js-bindings:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- name: "[m]-crypto-nodejs"
|
||||
path: "bindings/matrix-sdk-crypto-nodejs"
|
||||
- name: "[m]-crypto-js"
|
||||
path: "bindings/matrix-sdk-crypto-js"
|
||||
|
||||
name: lint ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ matrix.path }}
|
||||
run: npm install
|
||||
|
||||
- name: run lint
|
||||
working-directory: ${{ matrix.path }}
|
||||
run: npm run lint
|
||||
|
||||
test-matrix-sdk-crypto-nodejs:
|
||||
name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }}
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
@@ -27,7 +93,7 @@ jobs:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node-version: [14.0, 16.0, 18.0]
|
||||
node-version: [14.0, 16.0, 18.0, 19.0]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
@@ -44,14 +110,10 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
@@ -102,15 +164,12 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
profile: minimal
|
||||
override: true
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
@@ -135,43 +194,42 @@ jobs:
|
||||
|
||||
test-apple:
|
||||
name: matrix-rust-components-swift
|
||||
needs: xtask
|
||||
runs-on: macos-12
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# install protoc in case we end up rebuilding opentelemetry-proto
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install targets
|
||||
run: |
|
||||
rustup target add aarch64-apple-ios-sim --toolchain nightly
|
||||
rustup target add x86_64-apple-ios --toolchain nightly
|
||||
- name: Install aarch64-apple-ios target
|
||||
run: rustup target install aarch64-apple-ios
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Uniffi
|
||||
uses: actions-rs/cargo@v1
|
||||
- name: Get xtask
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
command: install
|
||||
# keep in sync with uniffi dependency in Cargo.toml's
|
||||
args: uniffi_bindgen --git https://github.com/mozilla/uniffi-rs --rev 091c3561656e72e1a4160412c83b36d98e556d06
|
||||
path: target/debug/xtask
|
||||
key: "${{ needs.xtask.outputs.cachekey-macos }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Generate .xcframework
|
||||
working-directory: bindings/apple
|
||||
run: sh ./debug_build_xcframework.sh x86_64 ci
|
||||
- name: Build library & bindings
|
||||
run: target/debug/xtask swift build-library
|
||||
|
||||
- name: Run XCTests
|
||||
working-directory: bindings/apple
|
||||
run: |
|
||||
xcodebuild test \
|
||||
-scheme MatrixRustSDK \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 13'
|
||||
run: swift test
|
||||
|
||||
- name: Build Framework
|
||||
run: target/debug/xtask swift build-framework --only-target=aarch64-apple-ios
|
||||
|
||||
+183
-148
@@ -12,46 +12,16 @@ on:
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
cancel-others:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.9.1
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
xtask:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check xtask cache
|
||||
uses: actions/cache@v3
|
||||
id: xtask-cache
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
|
||||
- name: Install rust stable toolchain
|
||||
if: steps.xtask-cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
if: steps.xtask-cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: -p xtask
|
||||
uses: ./.github/workflows/xtask.yml
|
||||
|
||||
test-matrix-sdk-features:
|
||||
name: 🐧 [m], ${{ matrix.name }}
|
||||
@@ -74,32 +44,35 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# use a separate cache for each job to work around
|
||||
# https://github.com/Swatinem/rust-cache/issues/124
|
||||
key: "${{ matrix.name }}"
|
||||
|
||||
# ... but only save the cache on the main branch
|
||||
# cf https://github.com/Swatinem/rust-cache/issues/95
|
||||
save-if: ${{ github.ref == 'refs/head/main' }}
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-features ${{ matrix.name }}
|
||||
run: |
|
||||
target/debug/xtask ci test-features ${{ matrix.name }}
|
||||
|
||||
test-matrix-sdk-examples:
|
||||
name: 🐧 [m]-examples
|
||||
@@ -112,29 +85,24 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci examples
|
||||
run: |
|
||||
target/debug/xtask ci examples
|
||||
|
||||
test-matrix-sdk-crypto:
|
||||
name: 🐧 [m]-crypto
|
||||
@@ -147,29 +115,24 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-crypto
|
||||
run: |
|
||||
target/debug/xtask ci test-crypto
|
||||
|
||||
test-all-crates:
|
||||
name: ${{ matrix.name }}
|
||||
@@ -194,32 +157,40 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
profile: minimal
|
||||
override: true
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Can't use `${{ matrix.* }}` inside uses
|
||||
- name: Install Rust stable
|
||||
if: matrix.rust == 'stable'
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install Rust beta
|
||||
if: matrix.rust == 'beta'
|
||||
uses: dtolnay/rust-toolchain@beta
|
||||
|
||||
- name: Install Rust nightly
|
||||
if: matrix.rust == 'nightly'
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: nextest
|
||||
args: run --workspace --exclude matrix-sdk-integration-testing
|
||||
run: |
|
||||
cargo nextest run --workspace \
|
||||
--exclude matrix-sdk-integration-testing --exclude sliding-sync-integration-test
|
||||
|
||||
- name: Test documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --doc
|
||||
run: |
|
||||
cargo test --doc --features docsrs
|
||||
|
||||
test-wasm:
|
||||
name: 🕸️ ${{ matrix.name }}
|
||||
@@ -264,42 +235,44 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
targets: wasm32-unknown-unknown
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
uses: jetli/wasm-pack-action@v0.4.0
|
||||
with:
|
||||
version: latest
|
||||
version: v0.10.3
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# use a separate cache for each job to work around
|
||||
# https://github.com/Swatinem/rust-cache/issues/124
|
||||
key: "${{ matrix.cmd }}"
|
||||
|
||||
# ... but only save the cache on the main branch
|
||||
# cf https://github.com/Swatinem/rust-cache/issues/95
|
||||
save-if: ${{ github.ref == 'refs/head/main' }}
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Rust Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci wasm ${{ matrix.cmd }}
|
||||
run: |
|
||||
target/debug/xtask ci wasm ${{ matrix.cmd }}
|
||||
|
||||
- name: Wasm-Pack test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci wasm-pack ${{ matrix.cmd }}
|
||||
run: |
|
||||
target/debug/xtask ci wasm-pack ${{ matrix.cmd }}
|
||||
|
||||
test-appservice:
|
||||
name: ${{ matrix.os-name }} [m]-appservice
|
||||
@@ -313,38 +286,35 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
xtask-cachekey: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
xtask-cachekey: "${{ needs.xtask.outputs.cachekey-macos }}"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ matrix.xtask-cachekey }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Run checks
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci test-appservice
|
||||
run: |
|
||||
target/debug/xtask ci test-appservice
|
||||
|
||||
formatting:
|
||||
name: Check Formatting
|
||||
@@ -356,18 +326,13 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
||||
run: |
|
||||
cargo fmt -- --check
|
||||
|
||||
typos:
|
||||
name: Spell Check with Typos
|
||||
@@ -379,7 +344,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check the spelling of the files in our repo
|
||||
uses: crate-ci/typos@master
|
||||
uses: crate-ci/typos@v1.13.0
|
||||
|
||||
clippy:
|
||||
name: Run clippy
|
||||
@@ -391,28 +356,29 @@ jobs:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Get xtask
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
key: xtask-${{ hashFiles('xtask/**') }}
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: -p xtask -- ci clippy
|
||||
run: |
|
||||
target/debug/xtask ci clippy
|
||||
|
||||
integration-tests:
|
||||
name: Integration test
|
||||
@@ -425,14 +391,10 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
@@ -448,7 +410,80 @@ jobs:
|
||||
disableRateLimiting: true
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
run: |
|
||||
cargo nextest run -p matrix-sdk-integration-testing
|
||||
|
||||
|
||||
sliding-sync-integration-tests:
|
||||
name: Sliding Sync Integration test
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
# run several docker containers with the same networking stack so the hostname 'postgres'
|
||||
# maps to the postgres container, etc.
|
||||
services:
|
||||
# sliding sync needs a postgres container
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
image: postgres
|
||||
# Provide the password for postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: syncv3
|
||||
# Set health checks to wait until postgres has started
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
# Maps tcp port 5432 on service container to the host
|
||||
- 5432:5432
|
||||
# run sliding sync and point it at the postgres container and synapse container.
|
||||
# the postgres container needs to be above this to make sure it has started prior to this service.
|
||||
slidingsync:
|
||||
image: "ghcr.io/matrix-org/sliding-sync:v0.99.0"
|
||||
env:
|
||||
SYNCV3_SERVER: "http://synapse:8008"
|
||||
SYNCV3_SECRET: "SUPER_CI_SECRET"
|
||||
SYNCV3_BINDADDR: ":8118"
|
||||
SYNCV3_DB: "user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres"
|
||||
ports:
|
||||
- 8118:8118
|
||||
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
|
||||
# latter does not provide networking for services to communicate with it.
|
||||
synapse:
|
||||
# Custom image built from https://github.com/matrix-org/synapse/tree/v1.72.0/docker/complement
|
||||
# with a dummy /complement/ca set
|
||||
image: ghcr.io/matrix-org/synapse-service:v1.72.0
|
||||
env:
|
||||
SYNAPSE_COMPLEMENT_DATABASE: sqlite
|
||||
SERVER_NAME: synapse
|
||||
ports:
|
||||
- 8008:8008
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
command: nextest
|
||||
args: run -p matrix-sdk-integration-testing
|
||||
python-version: 3.8
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
RUST_LOG: "hyper=trace"
|
||||
HOMESERVER_URL: "http://localhost:8008"
|
||||
HOMESERVER_DOMAIN: "synapse"
|
||||
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
|
||||
run: |
|
||||
cargo nextest run -p sliding-sync-integration-test
|
||||
|
||||
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
@@ -22,20 +26,15 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install tarpaulin
|
||||
uses: actions-rs/cargo@v1
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
command: install
|
||||
args: cargo-tarpaulin
|
||||
tool: cargo-tarpaulin
|
||||
|
||||
# set up backend for integration tests
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -50,10 +49,15 @@ jobs:
|
||||
serverName: "matrix-sdk.rs"
|
||||
|
||||
- name: Run tarpaulin
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: tarpaulin
|
||||
args: --out Xml
|
||||
run: |
|
||||
cargo tarpaulin --out Xml -e sliding-sync-integration-test
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
# Work around frequent upload errors, for runs inside the main repo (not PRs from forks).
|
||||
# Otherwise not required for public repos.
|
||||
token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
|
||||
# The upload sometimes fails due to https://github.com/codecov/codecov-action/issues/837.
|
||||
# To make sure that the failure gets flagged clearly in the UI, fail the action.
|
||||
fail_ci_if_error: true
|
||||
|
||||
@@ -5,6 +5,10 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: All crates
|
||||
@@ -16,11 +20,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
@@ -28,18 +28,16 @@ jobs:
|
||||
node-version: 18
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Keep in sync with xtask docs
|
||||
- name: Build rust documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
# Work around https://github.com/rust-lang/cargo/issues/10744
|
||||
CARGO_TARGET_APPLIES_TO_HOST: "true"
|
||||
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
|
||||
with:
|
||||
command: doc
|
||||
args: --no-deps --features docsrs
|
||||
run:
|
||||
cargo doc --no-deps --features docsrs
|
||||
|
||||
- name: Build `matrix-sdk-crypto-nodejs` doc
|
||||
run: |
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
uses: orhun/git-cliff-action@v1
|
||||
with:
|
||||
config: "${{ env.PKG_PATH }}/cliff.toml"
|
||||
args: "${{ inputs.previous_version }}..HEAD"
|
||||
args: "${{env.TAG_PREFIX}}${{ inputs.previous_version }}..HEAD"
|
||||
env:
|
||||
GIT_CLIFF_TAG: "${{ inputs.version }}"
|
||||
GIT_CLIFF_PREPEND: "${{ env.PKG_PATH }}/CHANGELOG.md"
|
||||
@@ -110,8 +110,10 @@ jobs:
|
||||
|
||||
trigger-release:
|
||||
# and trigger the tagging release workflow
|
||||
uses: matrix-org/matrix-rust-sdk/.github/workflows/release-crypto-nodejs.yml@main
|
||||
uses: ./.github/workflows/release-crypto-nodejs.yml
|
||||
needs: ['prepare-release']
|
||||
name: "Trigger release Workflow"
|
||||
with:
|
||||
tag: ${{needs.prepare-release.outputs.tag}}
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
@@ -27,6 +27,10 @@ on:
|
||||
description: "The tag to build with"
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
NPM_TOKEN:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
upload-assets:
|
||||
name: "Upload prebuilt libraries"
|
||||
@@ -48,6 +52,7 @@ jobs:
|
||||
apt_install: gcc-arm-linux-gnueabihf
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
apt_install: musl-tools
|
||||
# ----------------------------------- macOS
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
@@ -72,16 +77,13 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
if: "${{ !inputs.tag }}"
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
targets: ${{ matrix.target }}
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- if: ${{ matrix.apt_install }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -113,11 +115,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
if: "${{ !inputs.tag }}"
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Build lib
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# This workflow releases the `matrix-sdk-crypto-js` project.
|
||||
#
|
||||
# It is triggered when a new tag appears that matches
|
||||
# `matrix-sdk-crypto-js-v[0-9]+.*`. This workflow builds the package
|
||||
# for the binding, run its tests to ensure everything is still
|
||||
# correct, and publish the package on NPM and on a newly created
|
||||
# `matrix-sdk-crypto-js-[0-9]+.*`. This workflow builds the package
|
||||
# for the bindings, runs its tests to ensure everything is still
|
||||
# correct, and publishes the package on NPM and on a newly created
|
||||
# Github release.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ env:
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- matrix-sdk-crypto-js-v[0-9]+.*
|
||||
- matrix-sdk-crypto-js-[0-9]+.*
|
||||
|
||||
jobs:
|
||||
publish-matrix-sdk-crypto-js:
|
||||
@@ -29,15 +29,12 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
profile: minimal
|
||||
override: true
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v1
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
@@ -48,13 +45,12 @@ jobs:
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm install
|
||||
|
||||
- name: Configure NPM auth token
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm set "//registry.npmjs.org/:_authToken" "${{ secrets.NPM_TOKEN }}"
|
||||
|
||||
- name: Publish the WebAssembly + JavaScript binding (imply building + testing)
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
run: npm run publish
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
package: ${{env.PKG_PATH}}/package.json
|
||||
access: public
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Create the Github release
|
||||
uses: softprops/action-gh-release@v1
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# A reusable github actions workflow that will build xtask, if it is not
|
||||
# already cached.
|
||||
#
|
||||
# It will create a pair of GHA cache entries, if they do not already exist.
|
||||
# The cache keys take the form `xtask-{os}-{hash}`, where "{os}" is "linux"
|
||||
# or "macos", and "{hash}" is the hash of the xtask# directory.
|
||||
#
|
||||
# The cache keys are written to output variables named "cachekey-{os}".
|
||||
#
|
||||
|
||||
name: Build xtask if necessary
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
cachekey-linux:
|
||||
description: "The cache key for the linux build artifact"
|
||||
value: "${{ jobs.xtask.outputs.cachekey-linux }}"
|
||||
cachekey-macos:
|
||||
description: "The cache key for the macos build artifact"
|
||||
value: "${{ jobs.xtask.outputs.cachekey-macos }}"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
xtask:
|
||||
name: "xtask-${{ matrix.os-name }}"
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
cachekey-id: linux
|
||||
|
||||
- os: macos-12
|
||||
os-name: 🍏
|
||||
cachekey-id: macos
|
||||
|
||||
runs-on: "${{ matrix.os }}"
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Calculate cache key
|
||||
id: cachekey
|
||||
# set a step output variable "cachekey-{os}" that can be referenced in
|
||||
# the job outputs below.
|
||||
run: |
|
||||
echo "cachekey-${{ matrix.cachekey-id }}=xtask-${{ matrix.cachekey-id }}-${{ hashFiles('Cargo.toml', 'xtask/**') }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check xtask cache
|
||||
uses: actions/cache@v3
|
||||
id: xtask-cache
|
||||
with:
|
||||
path: target/debug/xtask
|
||||
# use the cache key calculated in the step above. Bit of an awkard
|
||||
# syntax
|
||||
key: |
|
||||
${{ steps.cachekey.outputs[format('cachekey-{0}', matrix.cachekey-id)] }}
|
||||
|
||||
- name: Install rust stable toolchain
|
||||
if: steps.xtask-cache.outputs.cache-hit != 'true'
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Build
|
||||
if: steps.xtask-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cargo build -p xtask
|
||||
|
||||
outputs:
|
||||
"cachekey-linux": "${{ steps.cachekey.outputs.cachekey-linux }}"
|
||||
"cachekey-macos": "${{ steps.cachekey.outputs.cachekey-macos }}"
|
||||
@@ -4,6 +4,9 @@ master.zip
|
||||
emsdk-*
|
||||
.idea/
|
||||
.env
|
||||
.build
|
||||
.swiftpm
|
||||
/Package.swift
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
@@ -11,3 +14,8 @@ xcuserdata/
|
||||
|
||||
## OS garbage
|
||||
.DS_Store
|
||||
|
||||
## Kotlin bindings generated files
|
||||
bindings/kotlin/.gradle/
|
||||
bindings/kotlin/buildSrc/build/
|
||||
bindings/kotlin/buildSrc/.gradle/
|
||||
|
||||
@@ -5,7 +5,9 @@ Fo = "Fo"
|
||||
BA = "BA"
|
||||
UE = "UE"
|
||||
Ure = "Ure"
|
||||
OFO = "OFO"
|
||||
Ot = "Ot"
|
||||
ket = "ket"
|
||||
# This is the thead html tag, remove this once typos is updated in the github
|
||||
# action. 1.3.1 seems to work correctly, while 1.11.0 on the CI seems to get
|
||||
# this wrong
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# Contributing to matrix-rust-sdk
|
||||
|
||||
## Chat rooms
|
||||
|
||||
In addition to our [main Matrix room], we have a development room at
|
||||
[#matrix-rust-sdk-dev:flipdot.org]. Please use it to discuss potential changes,
|
||||
the overall direction of development and related topics.
|
||||
|
||||
[main Matrix room]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
|
||||
[#matrix-rust-sdk-dev:flipdot.org]: https://matrix.to/#/#matrix-rust-sdk-dev:flipdot.org
|
||||
|
||||
## Testing
|
||||
|
||||
You can run most of the tests that also happen in CI also using
|
||||
`cargo xtask ci`. This needs a few dependencies to be installed, as it also runs
|
||||
automatic WASM tests:
|
||||
|
||||
```bash
|
||||
rustup component add clippy
|
||||
cargo install cargo-nextest typos-cli wasm-pack
|
||||
```
|
||||
|
||||
If you want to execute only one part of CI, there are a few sub-commands (see
|
||||
`cargo xtask ci --help`).
|
||||
|
||||
Some tests are not automatically run in `cargo xtask ci`, for example the
|
||||
integration tests that need a running synapse instance. These tests reside in
|
||||
`./testing/matrix-sdk-integration-testing`. See its
|
||||
[README](./testing/matrix-sdk-integration-testing/README.md) to easily set up a
|
||||
synapse for testing purposes.
|
||||
|
||||
## Sign off
|
||||
|
||||
In order to have a concrete record that your contribution is intentional
|
||||
and you agree to license it under the same terms as the project's license, we've
|
||||
adopted the same lightweight approach that the Linux Kernel
|
||||
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
|
||||
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
|
||||
projects use: the DCO (Developer Certificate of Origin:
|
||||
http://developercertificate.org/). This is a simple declaration that you wrote
|
||||
the contribution or otherwise have the right to contribute it to Matrix:
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
If you agree to this for your contribution, then all that's needed is to
|
||||
include the line in your commit or pull request comment:
|
||||
|
||||
```
|
||||
Signed-off-by: Your Name <your@email.example.org>
|
||||
```
|
||||
|
||||
We accept contributions under a legally identifiable name, such as your name on
|
||||
government documentation or common-law names (names claimed by legitimate usage
|
||||
or repute). Unfortunately, we cannot accept anonymous contributions at this
|
||||
time.
|
||||
|
||||
Git allows you to add this signoff automatically when using the `-s` flag to
|
||||
`git commit`, which uses the name and email set in your `user.name` and
|
||||
`user.email` git configs.
|
||||
|
||||
If you forgot to sign off your commits before making your pull request and are
|
||||
on Git 2.17+ you can mass signoff using rebase:
|
||||
|
||||
```
|
||||
git rebase --signoff origin/main
|
||||
```
|
||||
@@ -1,120 +0,0 @@
|
||||
# Conventional Commits
|
||||
|
||||
This project uses [Conventional
|
||||
Commits](https://www.conventionalcommits.org/). Read the
|
||||
[Summary](https://www.conventionalcommits.org/en/v1.0.0/#summary) or
|
||||
the [Full
|
||||
Specification](https://www.conventionalcommits.org/en/v1.0.0/#specification)
|
||||
to learn more.
|
||||
|
||||
## Types
|
||||
|
||||
Conventional Commits defines _type_ (as in `type(scope):
|
||||
message`). This section aims at listing the types used inside this
|
||||
project:
|
||||
|
||||
| Type | Definition |
|
||||
|-|-|
|
||||
| `feat` | About a new feature. |
|
||||
| `fix` | About a bug fix. |
|
||||
| `test` | About a test (suite, case, runner…). |
|
||||
| `doc` | About a documentation modification. |
|
||||
| `refactor` | About a refactoring. |
|
||||
| `ci` | About a Continuous Integration modification. |
|
||||
| `chore` | About some cleanup, or regular tasks. |
|
||||
|
||||
## Scopes
|
||||
|
||||
Conventional Commits defines _scope_ (as in `type(scope): message`). This
|
||||
section aims at listing all the scopes used inside this project:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th>Scope</th>
|
||||
<th>Definition</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="10">Crates</td>
|
||||
<td><code>sdk</code></td>
|
||||
<td>About the <code>matrix-sdk</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>appservice</code></td>
|
||||
<td>About the <code>matrix-sdk-appservice</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>base</code></td>
|
||||
<td>About the <code>matrix-sdk-base</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>common</code></td>
|
||||
<td>About the <code>matrix-sdk-common</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>indexeddb</code></td>
|
||||
<td>About the <code>matrix-sdk-indexeddb</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>qrcode</code></td>
|
||||
<td>About the <code>matrix-sdk-qrcode</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>sled</code></td>
|
||||
<td>About the <code>matrix-sdk-sled</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>store-encryption</code></td>
|
||||
<td>About the <code>matrix-sdk-store-encryption</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>test</code></td>
|
||||
<td>About the <code>matrix-sdk-test</code> and <code>matrix-sdk-test-macros</code> crate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">Bindings</td>
|
||||
<td><code>apple</code></td>
|
||||
<td>About the <code>matrix-rust-components-swift</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-nodejs</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-nodejs</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-js</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-js</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>crypto-ffi</code></td>
|
||||
<td>About the <code>matrix-sdk-crypto-ffi</code> binding.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Labs</td>
|
||||
<td><code>sled-state-inspector</code></td>
|
||||
<td>About the <code>sled-state-inspector</code> project.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Continuous Integration</td>
|
||||
<td><code>xtask</code></td>
|
||||
<td>About the <code>xtask</code> project.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Generating `CHANGELOG.md`
|
||||
|
||||
The [`git-cliff`](https://github.com/orhun/git-cliff) project is used
|
||||
to generate `CHANGELOG.md` automatically. Hence the various
|
||||
`cliff.toml` files that are present in this project, or the
|
||||
`package.metadata.git-cliff` sections in various `Cargo.toml` files.
|
||||
|
||||
Its companion,
|
||||
[`git-cliff-action`](https://github.com/orhun/git-cliff-action)
|
||||
project, is used inside Github Action workflows.
|
||||
Generated
+2033
-1307
File diff suppressed because it is too large
Load Diff
+50
-3
@@ -9,19 +9,51 @@ members = [
|
||||
"testing/*",
|
||||
"examples/*",
|
||||
"labs/*",
|
||||
"uniffi-bindgen",
|
||||
"xtask",
|
||||
]
|
||||
# xtask, labs, testing and the bindings should only be built when invoked explicitly.
|
||||
default-members = ["benchmarks", "crates/*"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.65"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.68"
|
||||
assert_matches = "1.5.0"
|
||||
async-stream = "0.3.3"
|
||||
async-trait = "0.1.60"
|
||||
base64 = "0.21.0"
|
||||
byteorder = "1.4.3"
|
||||
ctor = "0.1.26"
|
||||
dashmap = "5.2.0"
|
||||
eyeball = "0.4.0"
|
||||
eyeball-im = "0.2.0"
|
||||
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
|
||||
http = "0.2.6"
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "8eea3e05490fa9a318f9ed66c3a75272e6ef0ee5", features = ["client-api-c"] }
|
||||
ruma-common = { git = "https://github.com/ruma/ruma", rev = "8eea3e05490fa9a318f9ed66c3a75272e6ef0ee5" }
|
||||
once_cell = "1.16.0"
|
||||
serde = "1.0.151"
|
||||
serde_html_form = "0.2.0"
|
||||
serde_json = "1.0.91"
|
||||
thiserror = "1.0.38"
|
||||
tokio = { version = "1.24", default-features = false, features = ["sync"] }
|
||||
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
|
||||
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "41e94c0a4834137d8d359b829e2d4b334f5ab5b5" }
|
||||
uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "41e94c0a4834137d8d359b829e2d4b334f5ab5b5" }
|
||||
vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "fb609ca1e4df5a7a818490ae86ac694119e41e71" }
|
||||
zeroize = "1.3.0"
|
||||
|
||||
# Default release profile, select with `--release`
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
# Default development profile; default for most Cargo commands, otherwise
|
||||
# selected with `--debug`
|
||||
[profile.dev]
|
||||
# Copied from rust-analyzer. Saves a lot of disk space and hopefully
|
||||
# compilation time / mem usage too, at the expense of potentially having to
|
||||
# change this setting here when you want to use a debugger.
|
||||
# Saves a lot of disk space. If symbols are needed, use the dbg profile.
|
||||
debug = 0
|
||||
|
||||
[profile.dev.package]
|
||||
@@ -29,3 +61,18 @@ debug = 0
|
||||
# for the extra time of optimizing it for a clean build of matrix-sdk-ffi.
|
||||
quote = { opt-level = 2 }
|
||||
sha2 = { opt-level = 2 }
|
||||
|
||||
# Custom profile with full debugging info, use `--profile dbg` to select
|
||||
[profile.dbg]
|
||||
inherits = "dev"
|
||||
debug = 2
|
||||
|
||||
# Custom profile for use in (debug) builds of the binding crates, use
|
||||
# `--profile reldbg` to select
|
||||
[profile.reldbg]
|
||||
inherits = "dev"
|
||||
incremental = false
|
||||
|
||||
# Compile all non-workspace crate in the dependency tree with optimizations
|
||||
[profile.reldbg.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
[](https://codecov.io/gh/matrix-org/matrix-rust-sdk)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://matrix.to/#/#matrix-rust-sdk:matrix.org)
|
||||
@@ -26,7 +26,7 @@ The rust-sdk consists of multiple crates that can be picked at your convenience:
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.60`
|
||||
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.65`.
|
||||
|
||||
## Status
|
||||
|
||||
|
||||
@@ -3,22 +3,22 @@ name = "benchmarks"
|
||||
description = "Matrix SDK benchmarks"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
rust-version = "1.56"
|
||||
rust-version = { workspace = true }
|
||||
version = "1.0.0"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_reports"] }
|
||||
criterion = { version = "0.4.0", features = ["async", "async_tokio", "html_reports"] }
|
||||
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.6.0"}
|
||||
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.2.0", default-features = false, features = ["crypto-store"] }
|
||||
matrix-sdk-test = { path = "../testing/matrix-sdk-test", version = "0.6.0"}
|
||||
ruma = "0.7.0"
|
||||
serde_json = "1.0.79"
|
||||
ruma = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] }
|
||||
tokio = { version = "1.24.2", default-features = false, features = ["rt-multi-thread"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
pprof = { version = "0.10.0", features = ["flamegraph", "criterion"] }
|
||||
pprof = { version = "0.11.0", features = ["flamegraph", "criterion"] }
|
||||
|
||||
[[bench]]
|
||||
name = "crypto_bench"
|
||||
|
||||
@@ -71,7 +71,7 @@ pub fn keys_query(c: &mut Criterion) {
|
||||
});
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
|
||||
@@ -119,7 +119,7 @@ pub fn keys_claiming(c: &mut Criterion) {
|
||||
|| {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store =
|
||||
Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
|
||||
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store))
|
||||
@@ -181,7 +181,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
})
|
||||
});
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
|
||||
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
@@ -236,7 +236,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
|
||||
});
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Arc::new(SledCryptoStore::open_with_passphrase(dir.path(), None).unwrap());
|
||||
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
|
||||
|
||||
let machine =
|
||||
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
## Introduction
|
||||
**matrix-rust-sdk** leverages [UniFFI](https://mozilla.github.io/uniffi-rs/) to generate bindings for host languages (eg. Swift and Kotlin).
|
||||
|
||||
Rust code related with bindings live in the [matrix-rust-sdk/bindings](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings) folder.
|
||||
|
||||
Developers can expose Rust code to UniFFI using two different approaches:
|
||||
- Using an `.udl` file. When a crate has one, you find it under the `src` folder (an example is [here](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl)).
|
||||
- Add UniFFI directivies as Rust attributes. In this case Rust source files (`.rs`) contain attributes related to UniFFI (e.g. `#[uniffi::export]`). Attributes are preferred, where applicable.
|
||||
|
||||
|
||||
## Expose Rust definitions to UniFFI
|
||||
|
||||
### Check if the API is already on UniFFI
|
||||
|
||||
First of all check if the Rust definition you are looking for exists on UniFFI already. Most of exposed matrix definitions are collected in the crate [matrix-sdk-ffi](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings/matrix-sdk-ffi).
|
||||
This crate contains mainly small Rust wrappers around the actual Rust SDK (e.g. the crate [matrix-sdk](https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk))
|
||||
|
||||
If the Rust definition is on UniFFI already, you either:
|
||||
- find it in a `.udl` file like [matrix-sdk-ffi/src/api.udl](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl)
|
||||
- see it marked with a proper UniFFI Rust attribute like this `#[uniffi::export]`
|
||||
|
||||
|
||||
### Adding a missing matrix API
|
||||
|
||||
1. Unless you want to contribute on the crypto side, you probably need to add some code in the [matrix-sdk-ffi](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings/matrix-sdk-ffi) crate. After you find the crate you need to understand which file is best to contain the new Rust definition. When exposing new matrix API often (but not always) you need to touch the file [client.rs](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/client.rs)
|
||||
|
||||
2. Identify the API to expose in the target Rust crate (typically in [matrix-sdk](https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk). If you can’t find it, you probably need to touch the actual Rust SDK as well. In this case you typically just need to write some code around [ruma](https://github.com/ruma/ruma) (a Rust SDK’s dependency) which already implements most of the matrix protocol
|
||||
|
||||
3. After you got (by finding or writing) the required Rust code, you need to expose to UniFFI. To do that just write a small Rust wrapper in the related UniFFI crate (most of the time is **matrix-sdk-ffi**) you found on step 1.
|
||||
|
||||
4. When your new (wrapping) Rust definition is ready, remember to expose it to UniFFI.
|
||||
It’s best to do it using UniFFI Rust attributes (e.g. `#[uniffi::export]`). Otherwise add the new definition in the crate’s `.udl` file. For the **matrix-sdk-ffi** crate the definition file is [api.udl](https://github.com/matrix-org/matrix-rust-sdk/blob/main/bindings/matrix-sdk-ffi/src/api.udl). **Remember**: the language inside a `.udl` file isn’t Rust. To learn more about how map Rust into UDL read [here](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q**: I wrote my Rust code and exposed it to UniFFI. How can I check if the compiler is happy?\
|
||||
**A**: Run `cargo build` in the crate you touched (e.g. matrix-sdk-ffi). The compiler will complain if the Rust code and/or the `.udl` is wrong.
|
||||
|
||||
|
||||
**Q**: The compiler is happy with my code but the CI is failing on GitHub. How can I fix it?\
|
||||
**A**: The CI may fail for different reasons, you need to have a look on the failing GitHub workflow. One common reason though is that the linter ([Clippy](https://github.com/rust-lang/rust-clippy)) isn’t happy with your code. If this is the case, you can run `cargo clippy` in the crate you touched to see what’s wrong and fix it accordingly.
|
||||
+7
-4
@@ -5,18 +5,21 @@ maintained by the owners of the Matrix Rust SDK project.
|
||||
|
||||
* [`apple`] or `matrix-rust-components-swift`, Swift bindings of the
|
||||
[`matrix-sdk`] crate via [`matrix-sdk-ffi`],
|
||||
* [`matrix-sdk-crypto-ffi`], bindings of the [`matrix-sdk-crypto`]
|
||||
* [`matrix-sdk-crypto-ffi`], UniFFI (Kotlin, Swift, Python, Ruby) bindings of the [`matrix-sdk-crypto`]
|
||||
crate,
|
||||
* [`matrix-sdk-crypto-js`], JavaScript bindings of the
|
||||
[`matrix-sdk-crypto`] crate,
|
||||
* [`matrix-sdk-crypto-nodejs`], Node.js bindings of the
|
||||
[`matrix-sdk-crypto`] crate,
|
||||
* [`matrix-sdk-ffi`], bindings of the [`matrix-sdk`] crate,
|
||||
* [`matrix-sdk-ffi`], UniFFI bindings of the [`matrix-sdk`] crate.
|
||||
|
||||
[`apple`]: ./apple
|
||||
[`matrix-sdk-crypto-ffi`]: ./matrix-sdk-crypto-ffi
|
||||
[`matrix-sdk-crypto-js`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-crypto-nodejs`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-crypto-js`]: ./matrix-sdk-crypto-js
|
||||
[`matrix-sdk-crypto-nodejs`]: ./matrix-sdk-crypto-nodejs
|
||||
[`matrix-sdk-crypto`]: ../crates/matrix-sdk-crypto
|
||||
[`matrix-sdk-ffi`]: ./matrix-sdk-ffi
|
||||
[`matrix-sdk`]: ../crates/matrix-sdk
|
||||
|
||||
# Contributing
|
||||
To contribute read this [guide](./CONTRIBUTING.md).
|
||||
@@ -0,0 +1,8 @@
|
||||
// swift-tools-version:5.6
|
||||
|
||||
// A package manifest for local development. This file will be copied
|
||||
// into the root of the repo when generating an XCFramework.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package()
|
||||
@@ -0,0 +1,24 @@
|
||||
// swift-tools-version:5.6
|
||||
|
||||
// A package manifest for local development. This file will be copied
|
||||
// into the root of the repo when generating an XCFramework.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MatrixRustSDK",
|
||||
platforms: [
|
||||
.iOS(.v15),
|
||||
.macOS(.v12)
|
||||
],
|
||||
products: [
|
||||
.library(name: "MatrixRustSDK",
|
||||
targets: ["MatrixRustSDK"]),
|
||||
],
|
||||
targets: [
|
||||
.binaryTarget(name: "MatrixSDKFFI", path: "bindings/apple/generated/MatrixSDKFFI.xcframework"),
|
||||
.target(name: "MatrixRustSDK",
|
||||
dependencies: [.target(name: "MatrixSDKFFI")],
|
||||
path: "bindings/apple/generated/swift")
|
||||
]
|
||||
)
|
||||
@@ -1,13 +1,15 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "MatrixSDKCrypto"
|
||||
s.version = "0.1.0"
|
||||
s.version = "0.1.5"
|
||||
s.summary = "Uniffi based bindings for the Rust SDK crypto crate."
|
||||
s.homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" }
|
||||
s.author = { "matrix.org" => "support@matrix.org" }
|
||||
|
||||
s.ios.deployment_target = "11.0"
|
||||
s.osx.deployment_target = "10.10"
|
||||
|
||||
s.swift_versions = ['5.0']
|
||||
|
||||
s.source = { :http => "https://github.com/matrix-org/matrix-rust-sdk/releases/download/matrix-sdk-crypto-ffi-#{s.version}/MatrixSDKCryptoFFI.zip" }
|
||||
|
||||
@@ -5,17 +5,29 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MatrixRustSDK",
|
||||
platforms: [.iOS(.v15)],
|
||||
platforms: [
|
||||
.iOS(.v15),
|
||||
.macOS(.v12)
|
||||
],
|
||||
products: [
|
||||
.library(name: "MatrixRustSDK",
|
||||
targets: ["MatrixRustSDK"]),
|
||||
],
|
||||
targets: [
|
||||
.binaryTarget(name: "MatrixSDKFFI", path: "generated/MatrixSDKFFI.xcframework"),
|
||||
.target(name: "MatrixRustSDK",
|
||||
dependencies: [.target(name: "MatrixSDKFFI")],
|
||||
path: "generated/swift"),
|
||||
path: "generated/swift",
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-I", "./generated/matrix_sdk_ffi"])
|
||||
]),
|
||||
.testTarget(name: "MatrixRustSDKTests",
|
||||
dependencies: ["MatrixRustSDK"]),
|
||||
dependencies: ["MatrixRustSDK"],
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-I", "./generated/matrix_sdk_ffi"])
|
||||
],
|
||||
linkerSettings: [
|
||||
.linkedLibrary("matrix_sdk_ffi", .when(platforms: [.macOS])),
|
||||
.linkedLibrary("matrix_sdk_ffiFFI", .when(platforms: [.linux])),
|
||||
.unsafeFlags(["-L./generated/matrix_sdk_ffi"])
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
+20
-14
@@ -5,29 +5,32 @@ This project and build script demonstrate how to create an XCFramework that can
|
||||
## Prerequisites for building universal frameworks
|
||||
|
||||
* the Rust toolchain
|
||||
* UniFFI - `cargo install uniffi_bindgen`
|
||||
* Apple targets (e.g. `rustup target add aarch64-apple-ios`)
|
||||
* Apple targets (e.g. `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios aarch64-apple-darwin x86_64-apple-darwin`)
|
||||
* `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html)
|
||||
* `lipo` for creating the fat static libs
|
||||
|
||||
## Building the SDK
|
||||
|
||||
```
|
||||
sh build_xcframework.sh
|
||||
cargo xtask swift build-framework
|
||||
```
|
||||
|
||||
The `build_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
|
||||
The `build-framework` task will go through all the steps required to generate a fully usable `.xcframework`:
|
||||
|
||||
1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator, MacOS, and Mac Catalyst under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain.
|
||||
1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator and macOS under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain.
|
||||
2. `lipo` together the libraries for the same platform under `/generated`
|
||||
3. run `uniffi` and generate the C header, module map and swift files
|
||||
4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKFFI.xcframework`
|
||||
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
|
||||
|
||||
For development purposes, it will additionally generate a `Package.swift` file in the root of the repo that can be used to add the framework to your project and enable debugging through the use of [rust-xcode-plugin](https://github.com/BrainiumLLC/rust-xcode-plugin) (make sure to run the task with the argument `--profile=reldbg`).
|
||||
|
||||
When building the SDK for release you should pass the `--release` argument to the task, which will strip away any symbols and optimise the created binary.
|
||||
|
||||
## Building only the Crypto SDK
|
||||
|
||||
```
|
||||
sh build_crypto_xcframework.sh
|
||||
build_crypto_xcframework.sh
|
||||
```
|
||||
|
||||
The `build_crypto_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`:
|
||||
@@ -38,16 +41,19 @@ The `build_crypto_xcframework.sh` script will go through all the steps required
|
||||
4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKCryptoFFI.xcframework`
|
||||
5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework)
|
||||
|
||||
## Running the Xcode project
|
||||
## Building & testing the Swift package
|
||||
|
||||
The Xcode project is meant to provide a simple example on how to integrate everything together but also a place to run unit and integration tests from.
|
||||
The `Package.swift` file in this directory provides a simple example on how to integrate everything together but also a place to run unit and integration tests from.
|
||||
|
||||
It's pre-configured to link to the generated .xcframework and .swift files so successfully running the script first is necessary for it to compile.
|
||||
|
||||
It makes the compiled code available to swift by importing the C header through its bridging header.
|
||||
|
||||
Once all the generated components are available running it should be as easy as choosing a platform and clicking run.
|
||||
It's pre-configured to link to the generated static lib and .swift files so successfully running `cargo xtask swift build-library` first is necessary for it to compile. Afterwards you can execute the tests with `swift test`. Note that for the moment this only works on macOS but we're planning to add Linux support in the future.
|
||||
|
||||
## Distribution
|
||||
|
||||
The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a Swift package available [TBD](here) in the case of SDK, and as CocoaPods podspec in the case of Crypto SDK.
|
||||
The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a [Swift package](https://github.com/matrix-org/matrix-rust-components-swift/) in the case of SDK, and as a CocoaPods podspec in the case of Crypto SDK.
|
||||
|
||||
### Publishing MatrixSDKCrypto
|
||||
1. Run `build_crypto_xcframework.sh` script which generates a .zip file with the framework
|
||||
2. Increment the version in `MatrixSDKCrypto.podspec`
|
||||
3. Create a new [GitHub release](https://github.com/matrix-org/matrix-rust-sdk/releases) with the same version (see [example](https://github.com/matrix-org/matrix-rust-sdk/releases/tag/matrix-sdk-crypto-ffi-0.1.0) for naming)
|
||||
4. Upload the .zip file to this release
|
||||
5. Push new Podspec version to Cocoapods via `pod trunk push MatrixSDKCrypto.podspec --allow-warnings`
|
||||
|
||||
@@ -11,6 +11,17 @@ final class ClientTests: XCTestCase {
|
||||
XCTFail("The client should build successfully when given a homeserver.")
|
||||
}
|
||||
}
|
||||
|
||||
func testBuildingWithHomeserverURLAndUserAgent() {
|
||||
do {
|
||||
_ = try ClientBuilder()
|
||||
.homeserverUrl(url: "https://localhost:8008")
|
||||
.userAgent(userAgent: "golden-eye/007")
|
||||
.build()
|
||||
} catch {
|
||||
XCTFail("The client should build successfully when given a homeserver and user agent.")
|
||||
}
|
||||
}
|
||||
|
||||
func testBuildingWithUsername() {
|
||||
do {
|
||||
|
||||
@@ -10,7 +10,7 @@ TARGET_DIR="${SRC_ROOT}/target"
|
||||
|
||||
GENERATED_DIR="${SRC_ROOT}/generated"
|
||||
if [ -d "${GENERATED_DIR}" ]; then rm -rf "${GENERATED_DIR}"; fi
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
mkdir -p ${GENERATED_DIR}/{macos,simulator}
|
||||
|
||||
REL_FLAG="--release"
|
||||
REL_TYPE_DIR="release"
|
||||
@@ -20,22 +20,43 @@ TARGET_CRATE=matrix-sdk-crypto-ffi
|
||||
# Build static libs for all the different architectures
|
||||
|
||||
# iOS
|
||||
echo -e "Building for iOS [1/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios"
|
||||
|
||||
# MacOS
|
||||
echo -e "\nBuilding for macOS (Apple Silicon) [2/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-darwin"
|
||||
echo -e "\nBuilding for macOS (Intel) [3/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-darwin"
|
||||
|
||||
# iOS Simulator
|
||||
echo -e "\nBuilding for iOS Simulator (Apple Silicon) [4/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
echo -e "\nBuilding for iOS Simulator (Intel) [5/5]"
|
||||
cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
|
||||
echo -e "\nCreating XCFramework"
|
||||
# Lipo together the libraries for the same platform
|
||||
|
||||
# MacOS
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
-output "${GENERATED_DIR}/macos/libmatrix_sdk_crypto_ffi.a"
|
||||
|
||||
# iOS Simulator
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_crypto_ffi.a"
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
-output "${GENERATED_DIR}/simulator/libmatrix_sdk_crypto_ffi.a"
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate "${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl" --language swift --config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" --out-dir ${GENERATED_DIR}
|
||||
cargo uniffi-bindgen generate \
|
||||
--language swift \
|
||||
--lib-file "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
--config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" \
|
||||
--out-dir ${GENERATED_DIR} \
|
||||
"${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl"
|
||||
|
||||
# Move headers to the right place
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
@@ -55,19 +76,23 @@ mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
|
||||
if [ -d "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"; fi
|
||||
|
||||
xcodebuild -create-xcframework \
|
||||
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_crypto_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/libmatrix_crypto_ffi.a" \
|
||||
-library "${GENERATED_DIR}/macos/libmatrix_sdk_crypto_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/simulator/libmatrix_sdk_crypto_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-output "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"
|
||||
|
||||
# Cleanup
|
||||
|
||||
if [ -f "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a"; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_crypto_ffi.a"; fi
|
||||
if [ -d "${GENERATED_DIR}/macos" ]; then rm -rf "${GENERATED_DIR}/macos"; fi
|
||||
if [ -d "${GENERATED_DIR}/simulator" ]; then rm -rf "${GENERATED_DIR}/simulator"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
|
||||
# Zip up framework, sources and LICENSE, ready to be uploaded to GitHub Releases and used by MatrixSDKCrypto.podspec
|
||||
cp ${SRC_ROOT}/LICENSE $GENERATED_DIR
|
||||
cd $GENERATED_DIR
|
||||
zip -r MatrixSDKCryptoFFI.zip MatrixSDKCryptoFFI.xcframework Sources LICENSE
|
||||
rm LICENSE
|
||||
|
||||
echo "XCFramework is ready 🚀"
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Path to the repo root
|
||||
SRC_ROOT=../..
|
||||
|
||||
TARGET_DIR="${SRC_ROOT}/target"
|
||||
|
||||
GENERATED_DIR="${SRC_ROOT}/generated"
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
REL_FLAG="--release"
|
||||
REL_TYPE_DIR="release"
|
||||
|
||||
# Build static libs for all the different architectures
|
||||
|
||||
# iOS
|
||||
echo -e "Building for iOS [1/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios"
|
||||
|
||||
# MacOS
|
||||
echo -e "\nBuilding for macOS (Apple Silicon) [2/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-darwin"
|
||||
echo -e "\nBuilding for macOS (Intel) [3/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin"
|
||||
|
||||
# iOS Simulator
|
||||
echo -e "\nBuilding for iOS Simulator (Apple Silicon) [4/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
|
||||
echo -e "\nBuilding for iOS Simulator (Intel) [5/5]"
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
|
||||
|
||||
echo -e "\nCreating XCFramework"
|
||||
# Lipo together the libraries for the same platform
|
||||
|
||||
# MacOS
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"
|
||||
|
||||
# iOS Simulator
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
|
||||
# Generate uniffi files
|
||||
# Architecture for the .a file argument doesn't matter, since the API is the same on all
|
||||
uniffi-bindgen generate \
|
||||
--language swift \
|
||||
--lib-file "${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
--out-dir ${GENERATED_DIR} \
|
||||
"${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl"
|
||||
|
||||
# Move them to the right place
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
mkdir -p ${HEADERS_DIR}
|
||||
|
||||
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
SWIFT_DIR="${GENERATED_DIR}/swift"
|
||||
mkdir -p ${SWIFT_DIR}
|
||||
|
||||
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
|
||||
|
||||
# Build the xcframework
|
||||
|
||||
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
|
||||
|
||||
xcodebuild -create-xcframework \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-output "${GENERATED_DIR}/MatrixSDKFFI.xcframework"
|
||||
|
||||
# Cleanup
|
||||
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"; fi
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
IS_CI=false
|
||||
ACTIVE_ARCH="arm64"
|
||||
|
||||
if [ $# -eq 2 ]; then
|
||||
echo "Running CI build"
|
||||
IS_CI=true
|
||||
ARCHS=( $1 )
|
||||
ACTIVE_ARCH=${ARCHS[0]}
|
||||
elif [ $# -eq 1 ]; then
|
||||
echo "Running debug build"
|
||||
ARCHS=( $1 )
|
||||
ACTIVE_ARCH=${ARCHS[0]}
|
||||
else
|
||||
echo "Running debug build"
|
||||
fi
|
||||
|
||||
echo "Active architecture ${ACTIVE_ARCH}"
|
||||
|
||||
# Path to the repo root
|
||||
SRC_ROOT=../..
|
||||
|
||||
TARGET_DIR="${SRC_ROOT}/target"
|
||||
|
||||
GENERATED_DIR="${SRC_ROOT}/bindings/apple/generated"
|
||||
mkdir -p ${GENERATED_DIR}
|
||||
|
||||
REL_FLAG=""
|
||||
REL_TYPE_DIR="debug"
|
||||
|
||||
# iOS Simulator arm64
|
||||
if [ "$ACTIVE_ARCH" = "arm64" ]; then
|
||||
TARGET="aarch64-apple-ios-sim"
|
||||
# iOS Simulator intel
|
||||
else
|
||||
TARGET="x86_64-apple-ios"
|
||||
fi
|
||||
|
||||
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "$TARGET"
|
||||
|
||||
lipo -create \
|
||||
"${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
|
||||
|
||||
# Generate uniffi files
|
||||
uniffi-bindgen generate \
|
||||
--language swift \
|
||||
--lib-file "${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
|
||||
--out-dir ${GENERATED_DIR} \
|
||||
"${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl"
|
||||
|
||||
# Move them to the right place
|
||||
HEADERS_DIR=${GENERATED_DIR}/headers
|
||||
mkdir -p ${HEADERS_DIR}
|
||||
|
||||
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
|
||||
|
||||
# Rename and move modulemap to the right place
|
||||
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
|
||||
|
||||
SWIFT_DIR="${GENERATED_DIR}/swift"
|
||||
mkdir -p ${SWIFT_DIR}
|
||||
|
||||
mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR}
|
||||
|
||||
# Build the xcframework
|
||||
|
||||
if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi
|
||||
|
||||
xcodebuild -create-xcframework \
|
||||
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
|
||||
-headers ${HEADERS_DIR} \
|
||||
-output "${GENERATED_DIR}/MatrixSDKFFI.xcframework"
|
||||
|
||||
# Cleanup
|
||||
|
||||
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
|
||||
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
|
||||
|
||||
if [ "$IS_CI" = false ] ; then
|
||||
echo "Preparing matrix-rust-components-swift"
|
||||
|
||||
# Debug -> Copy generated files over to ../../../matrix-rust-components-swift
|
||||
rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/"
|
||||
rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK"
|
||||
fi
|
||||
@@ -3,7 +3,7 @@ name = "matrix-sdk-crypto-ffi"
|
||||
version = "0.1.0"
|
||||
authors = ["Damir Jelić <poljar@termina.org.uk>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
rust-version = { workspace = true }
|
||||
description = "Uniffi based bindings for the Rust SDK crypto crate"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
license = "Apache-2.0"
|
||||
@@ -11,25 +11,30 @@ publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
name = "matrix_crypto_ffi"
|
||||
|
||||
[features]
|
||||
default = ["bundled-sqlite"]
|
||||
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
base64 = "0.13.0"
|
||||
anyhow = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
futures-util = "0.3.25"
|
||||
hmac = "0.12.1"
|
||||
http = "0.2.6"
|
||||
http = { workspace = true }
|
||||
pbkdf2 = "0.11.0"
|
||||
rand = "0.8.5"
|
||||
ruma = { version = "0.7.0", features = ["client-api-c"] }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
ruma = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = "0.10.2"
|
||||
thiserror = "1.0.30"
|
||||
tracing = "0.1.34"
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
|
||||
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
|
||||
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
|
||||
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
uniffi = { workspace = true }
|
||||
vodozemac = { workspace = true }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
[dependencies.js_int]
|
||||
version = "0.2.2"
|
||||
@@ -44,22 +49,19 @@ path = "../../crates/matrix-sdk-crypto"
|
||||
version = "0.6.0"
|
||||
features = ["qrcode", "backups_v1"]
|
||||
|
||||
[dependencies.matrix-sdk-sled]
|
||||
path = "../../crates/matrix-sdk-sled"
|
||||
version = "0.2.0"
|
||||
[dependencies.matrix-sdk-sqlite]
|
||||
path = "../../crates/matrix-sdk-sqlite"
|
||||
version = "0.1.0"
|
||||
default_features = false
|
||||
features = ["crypto-store"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.17.0"
|
||||
version = "1.24.2"
|
||||
default_features = false
|
||||
features = ["rt-multi-thread"]
|
||||
|
||||
[dependencies.vodozemac]
|
||||
version = "0.3.0"
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06", features = ["builtin-bindgen"] }
|
||||
uniffi = { workspace = true, features = ["build"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
|
||||
@@ -71,7 +71,7 @@ $ cp ../../target/aarch64-linux-android/debug/libmatrix_crypto.so \
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.60`.
|
||||
These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.62`.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
uniffi_build::generate_scaffolding("./src/olm.udl").unwrap();
|
||||
uniffi::generate_scaffolding("./src/olm.udl").unwrap();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ pub struct BackupRecoveryKey {
|
||||
}
|
||||
|
||||
/// Error type for the decryption of backed up room keys.
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
pub enum PkDecryptionError {
|
||||
/// An internal libolm error happened during decryption.
|
||||
#[error("Error decryption a PkMessage {0}")]
|
||||
@@ -48,6 +49,7 @@ pub struct PassphraseInfo {
|
||||
}
|
||||
|
||||
/// The public part of the backup key.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct MegolmV1BackupKey {
|
||||
/// The actual base64 encoded public key.
|
||||
pub public_key: String,
|
||||
@@ -59,6 +61,19 @@ pub struct MegolmV1BackupKey {
|
||||
pub backup_algorithm: String,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl BackupRecoveryKey {
|
||||
/// Convert the recovery key to a base 58 encoded string.
|
||||
pub fn to_base58(&self) -> String {
|
||||
self.inner.to_base58()
|
||||
}
|
||||
|
||||
/// Convert the recovery key to a base 64 encoded string.
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
impl BackupRecoveryKey {
|
||||
const KEY_SIZE: usize = 32;
|
||||
const SALT_SIZE: usize = 32;
|
||||
@@ -115,7 +130,10 @@ impl BackupRecoveryKey {
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl BackupRecoveryKey {
|
||||
/// Get the public part of the backup key.
|
||||
pub fn megolm_v1_public_key(&self) -> MegolmV1BackupKey {
|
||||
let public_key = self.inner.megolm_v1_public_key();
|
||||
@@ -134,16 +152,6 @@ impl BackupRecoveryKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the recovery key to a base 58 encoded string.
|
||||
pub fn to_base58(&self) -> String {
|
||||
self.inner.to_base58()
|
||||
}
|
||||
|
||||
/// Convert the recovery key to a base 64 encoded string.
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
|
||||
/// Try to decrypt a message that was encrypted using the public part of the
|
||||
/// backup key.
|
||||
pub fn decrypt_v1(
|
||||
|
||||
@@ -4,6 +4,7 @@ use matrix_sdk_crypto::{
|
||||
store::CryptoStoreError as InnerStoreError, KeyExportError, MegolmError, OlmError,
|
||||
SecretImportError as RustSecretImportError, SignatureError as InnerSignatureError,
|
||||
};
|
||||
use matrix_sdk_sqlite::OpenStoreError;
|
||||
use ruma::{IdParseError, OwnedUserId};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -40,6 +41,8 @@ pub enum SignatureError {
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CryptoStoreError {
|
||||
#[error("Failed to open the store")]
|
||||
OpenStore(#[from] OpenStoreError),
|
||||
#[error(transparent)]
|
||||
CryptoStore(#[from] InnerStoreError),
|
||||
#[error(transparent)]
|
||||
@@ -52,12 +55,41 @@ pub enum CryptoStoreError {
|
||||
Identifier(#[from] IdParseError),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum DecryptionError {
|
||||
#[error(transparent)]
|
||||
Serialization(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
Identifier(#[from] IdParseError),
|
||||
#[error(transparent)]
|
||||
Megolm(#[from] MegolmError),
|
||||
Serialization { error: String },
|
||||
Identifier { error: String },
|
||||
Megolm { error: String },
|
||||
MissingRoomKey { error: String, withheld_code: Option<String> },
|
||||
Store { error: String },
|
||||
}
|
||||
|
||||
impl From<MegolmError> for DecryptionError {
|
||||
fn from(value: MegolmError) -> Self {
|
||||
match value {
|
||||
MegolmError::MissingRoomKey(withheld_code) => Self::MissingRoomKey {
|
||||
error: "Withheld Inbound group session".to_owned(),
|
||||
withheld_code: withheld_code.map(|w| w.to_string()),
|
||||
},
|
||||
_ => Self::Megolm { error: value.to_string() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for DecryptionError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
Self::Serialization { error: err.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdParseError> for DecryptionError {
|
||||
fn from(err: IdParseError) -> Self {
|
||||
Self::Identifier { error: err.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InnerStoreError> for DecryptionError {
|
||||
fn from(err: InnerStoreError) -> Self {
|
||||
Self::Store { error: err.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! client or client library in any of the language targets Uniffi supports.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![allow(unused_qualifications)]
|
||||
|
||||
mod backup_recovery_key;
|
||||
mod device;
|
||||
@@ -14,7 +15,7 @@ mod responses;
|
||||
mod users;
|
||||
mod verification;
|
||||
|
||||
use std::{borrow::Borrow, collections::HashMap, str::FromStr, sync::Arc};
|
||||
use std::{borrow::Borrow, collections::HashMap, str::FromStr, sync::Arc, time::Duration};
|
||||
|
||||
pub use backup_recovery_key::{
|
||||
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
|
||||
@@ -25,19 +26,33 @@ pub use error::{
|
||||
};
|
||||
use js_int::UInt;
|
||||
pub use logger::{set_logger, Logger};
|
||||
pub use machine::{KeyRequestPair, OlmMachine};
|
||||
use matrix_sdk_crypto::types::{EventEncryptionAlgorithm, SigningKey};
|
||||
pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification};
|
||||
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
|
||||
use matrix_sdk_crypto::{
|
||||
backups::SignatureState,
|
||||
olm::{IdentityKeys, InboundGroupSession, Session},
|
||||
store::{Changes, CryptoStore, RoomSettings as RustRoomSettings},
|
||||
types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey},
|
||||
EncryptionSettings as RustEncryptionSettings, LocalTrust,
|
||||
};
|
||||
use matrix_sdk_sqlite::SqliteCryptoStore;
|
||||
pub use responses::{
|
||||
BootstrapCrossSigningResult, DeviceLists, KeysImportResult, OutgoingVerificationRequest,
|
||||
Request, RequestType, SignatureUploadRequest, UploadSigningKeysRequest,
|
||||
};
|
||||
use ruma::{DeviceId, DeviceKeyAlgorithm, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UserId};
|
||||
use ruma::{
|
||||
events::room::history_visibility::HistoryVisibility as RustHistoryVisibility, DeviceId,
|
||||
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, RoomId, SecondsSinceUnixEpoch, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::runtime::Runtime;
|
||||
pub use users::UserIdentity;
|
||||
pub use verification::{
|
||||
CancelInfo, ConfirmVerificationResult, QrCode, RequestVerificationResult, Sas, ScanResult,
|
||||
StartSasResult, Verification, VerificationRequest,
|
||||
CancelInfo, ConfirmVerificationResult, QrCode, QrCodeListener, QrCodeState,
|
||||
RequestVerificationResult, Sas, SasListener, SasState, ScanResult, StartSasResult,
|
||||
Verification, VerificationRequest, VerificationRequestListener, VerificationRequestState,
|
||||
};
|
||||
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
|
||||
|
||||
/// Struct collecting data that is important to migrate to the rust-sdk
|
||||
#[derive(Deserialize, Serialize)]
|
||||
@@ -58,6 +73,26 @@ pub struct MigrationData {
|
||||
cross_signing: CrossSigningKeyExport,
|
||||
/// The list of users that the Rust SDK should track.
|
||||
tracked_users: Vec<String>,
|
||||
/// Map of room settings
|
||||
room_settings: HashMap<String, RoomSettings>,
|
||||
}
|
||||
|
||||
/// Struct collecting data that is important to migrate sessions to the rust-sdk
|
||||
pub struct SessionMigrationData {
|
||||
/// The user id that the data belongs to.
|
||||
user_id: String,
|
||||
/// The device id that the data belongs to.
|
||||
device_id: String,
|
||||
/// The Curve25519 public key of the Account that owns this data.
|
||||
curve25519_key: String,
|
||||
/// The Ed25519 public key of the Account that owns this data.
|
||||
ed25519_key: String,
|
||||
/// The list of pickleds Olm Sessions.
|
||||
sessions: Vec<PickledSession>,
|
||||
/// The list of pickled Megolm inbound group sessions.
|
||||
inbound_group_sessions: Vec<PickledInboundGroupSession>,
|
||||
/// The Olm pickle key that was used to pickle all the Olm objects.
|
||||
pickle_key: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A pickled version of an `Account`.
|
||||
@@ -137,38 +172,42 @@ impl From<anyhow::Error> for MigrationError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate a libolm based setup to a vodozemac based setup stored in a Sled
|
||||
/// Migrate a libolm based setup to a vodozemac based setup stored in a SQLite
|
||||
/// store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - The data that should be migrated over to the Sled store.
|
||||
/// * `data` - The data that should be migrated over to the SQLite store.
|
||||
///
|
||||
/// * `path` - The path where the Sled store should be created.
|
||||
/// * `path` - The path where the SQLite store should be created.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that should be used to encrypt the data at
|
||||
/// rest in the Sled store. **Warning**, if no passphrase is given, the store
|
||||
/// rest in the SQLite store. **Warning**, if no passphrase is given, the store
|
||||
/// and all its data will remain unencrypted.
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the migration.
|
||||
pub fn migrate(
|
||||
data: MigrationData,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
let runtime = Runtime::new()?;
|
||||
runtime.block_on(async move {
|
||||
migrate_data(data, path, passphrase, progress_listener).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
async fn migrate_data(
|
||||
mut data: MigrationData,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
use matrix_sdk_crypto::{
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
store::{Changes as RustChanges, CryptoStore, RecoveryKey},
|
||||
};
|
||||
use matrix_sdk_sled::SledCryptoStore;
|
||||
use tokio::runtime::Runtime;
|
||||
use vodozemac::{
|
||||
megolm::InboundGroupSession,
|
||||
olm::{Account, Session},
|
||||
Curve25519PublicKey,
|
||||
};
|
||||
use matrix_sdk_crypto::{olm::PrivateCrossSigningIdentity, store::RecoveryKey};
|
||||
use vodozemac::olm::Account;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// The total steps here include all the sessions/inbound group sessions and
|
||||
@@ -185,8 +224,7 @@ pub fn migrate(
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let store = SledCryptoStore::open_with_passphrase(path, passphrase.as_deref())?;
|
||||
let runtime = Runtime::new()?;
|
||||
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
@@ -215,11 +253,170 @@ pub fn migrate(
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let (sessions, inbound_group_sessions) = collect_sessions(
|
||||
processed_steps,
|
||||
total_steps,
|
||||
&listener,
|
||||
&data.pickle_key,
|
||||
user_id.clone(),
|
||||
device_id,
|
||||
identity_keys,
|
||||
data.sessions,
|
||||
data.inbound_group_sessions,
|
||||
)?;
|
||||
|
||||
let recovery_key =
|
||||
data.backup_recovery_key.map(|k| RecoveryKey::from_base58(k.as_str())).transpose()?;
|
||||
|
||||
let cross_signing = PrivateCrossSigningIdentity::empty((*user_id).into());
|
||||
cross_signing
|
||||
.import_secrets_unchecked(
|
||||
data.cross_signing.master_key.as_deref(),
|
||||
data.cross_signing.self_signing_key.as_deref(),
|
||||
data.cross_signing.user_signing_key.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
data.cross_signing.master_key.zeroize();
|
||||
data.cross_signing.self_signing_key.zeroize();
|
||||
data.cross_signing.user_signing_key.zeroize();
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let tracked_users: Vec<_> = data
|
||||
.tracked_users
|
||||
.into_iter()
|
||||
.map(|u| Ok(((parse_user_id(&u)?), true)))
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
let tracked_users: Vec<_> = tracked_users.iter().map(|(u, d)| (&**u, *d)).collect();
|
||||
store.save_tracked_users(tracked_users.as_slice()).await?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let mut room_settings = HashMap::new();
|
||||
for (room_id, settings) in data.room_settings {
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
room_settings.insert(room_id, settings.into());
|
||||
}
|
||||
|
||||
let changes = Changes {
|
||||
account: Some(account),
|
||||
private_identity: Some(cross_signing),
|
||||
sessions,
|
||||
inbound_group_sessions,
|
||||
recovery_key,
|
||||
backup_version: data.backup_version,
|
||||
room_settings,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
save_changes(processed_steps, total_steps, &listener, changes, &store).await
|
||||
}
|
||||
|
||||
async fn save_changes(
|
||||
mut processed_steps: usize,
|
||||
total_steps: usize,
|
||||
listener: &dyn Fn(usize, usize),
|
||||
changes: Changes,
|
||||
store: &SqliteCryptoStore,
|
||||
) -> anyhow::Result<()> {
|
||||
store.save_changes(changes).await?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Migrate sessions and group sessions of a libolm based setup to a vodozemac
|
||||
/// based setup stored in a SQLite store.
|
||||
///
|
||||
/// This method allows you to migrate a subset of the data, it should only be
|
||||
/// used after the [`migrate()`] method has been already used.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - The data that should be migrated over to the SQLite store.
|
||||
///
|
||||
/// * `path` - The path where the SQLite store should be created.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that should be used to encrypt the data at
|
||||
/// rest in the SQLite store. **Warning**, if no passphrase is given, the store
|
||||
/// and all its data will remain unencrypted.
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the migration.
|
||||
pub fn migrate_sessions(
|
||||
data: SessionMigrationData,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
let runtime = Runtime::new()?;
|
||||
runtime.block_on(migrate_session_data(data, path, passphrase, progress_listener))
|
||||
}
|
||||
|
||||
async fn migrate_session_data(
|
||||
data: SessionMigrationData,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> anyhow::Result<()> {
|
||||
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
|
||||
|
||||
let listener = |progress: usize, total: usize| {
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let total_steps = 1 + data.sessions.len() + data.inbound_group_sessions.len();
|
||||
let processed_steps = 0;
|
||||
|
||||
let user_id = UserId::parse(data.user_id)?.into();
|
||||
let device_id: OwnedDeviceId = data.device_id.into();
|
||||
|
||||
let identity_keys = IdentityKeys {
|
||||
ed25519: Ed25519PublicKey::from_base64(&data.ed25519_key)?,
|
||||
curve25519: Curve25519PublicKey::from_base64(&data.curve25519_key)?,
|
||||
}
|
||||
.into();
|
||||
|
||||
let (sessions, inbound_group_sessions) = collect_sessions(
|
||||
processed_steps,
|
||||
total_steps,
|
||||
&listener,
|
||||
&data.pickle_key,
|
||||
user_id,
|
||||
device_id.into(),
|
||||
identity_keys,
|
||||
data.sessions,
|
||||
data.inbound_group_sessions,
|
||||
)?;
|
||||
|
||||
let changes = Changes { sessions, inbound_group_sessions, ..Default::default() };
|
||||
save_changes(processed_steps, total_steps, &listener, changes, &store).await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn collect_sessions(
|
||||
mut processed_steps: usize,
|
||||
total_steps: usize,
|
||||
listener: &dyn Fn(usize, usize),
|
||||
pickle_key: &[u8],
|
||||
user_id: Arc<UserId>,
|
||||
device_id: Arc<DeviceId>,
|
||||
identity_keys: Arc<IdentityKeys>,
|
||||
session_pickles: Vec<PickledSession>,
|
||||
group_session_pickles: Vec<PickledInboundGroupSession>,
|
||||
) -> anyhow::Result<(Vec<Session>, Vec<InboundGroupSession>)> {
|
||||
let mut sessions = Vec::new();
|
||||
|
||||
for session_pickle in data.sessions {
|
||||
for session_pickle in session_pickles {
|
||||
let pickle =
|
||||
Session::from_libolm_pickle(&session_pickle.pickle, &data.pickle_key)?.pickle();
|
||||
vodozemac::olm::Session::from_libolm_pickle(&session_pickle.pickle, pickle_key)?
|
||||
.pickle();
|
||||
|
||||
let creation_time = SecondsSinceUnixEpoch(UInt::from_str(&session_pickle.creation_time)?);
|
||||
let last_use_time = SecondsSinceUnixEpoch(UInt::from_str(&session_pickle.last_use_time)?);
|
||||
@@ -232,12 +429,8 @@ pub fn migrate(
|
||||
last_use_time,
|
||||
};
|
||||
|
||||
let session = matrix_sdk_crypto::olm::Session::from_pickle(
|
||||
user_id.clone(),
|
||||
device_id.clone(),
|
||||
identity_keys.clone(),
|
||||
pickle,
|
||||
);
|
||||
let session =
|
||||
Session::from_pickle(user_id.clone(), device_id.clone(), identity_keys.clone(), pickle);
|
||||
|
||||
sessions.push(session);
|
||||
processed_steps += 1;
|
||||
@@ -246,9 +439,12 @@ pub fn migrate(
|
||||
|
||||
let mut inbound_group_sessions = Vec::new();
|
||||
|
||||
for session in data.inbound_group_sessions {
|
||||
let pickle =
|
||||
InboundGroupSession::from_libolm_pickle(&session.pickle, &data.pickle_key)?.pickle();
|
||||
for session in group_session_pickles {
|
||||
let pickle = vodozemac::megolm::InboundGroupSession::from_libolm_pickle(
|
||||
&session.pickle,
|
||||
pickle_key,
|
||||
)?
|
||||
.pickle();
|
||||
|
||||
let sender_key = Curve25519PublicKey::from_base64(&session.sender_key)?;
|
||||
|
||||
@@ -269,7 +465,7 @@ pub fn migrate(
|
||||
imported: session.imported,
|
||||
backed_up: session.backed_up,
|
||||
history_visibility: None,
|
||||
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
algorithm: RustEventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
};
|
||||
|
||||
let session = matrix_sdk_crypto::olm::InboundGroupSession::from_pickle(pickle)?;
|
||||
@@ -279,51 +475,46 @@ pub fn migrate(
|
||||
listener(processed_steps, total_steps);
|
||||
}
|
||||
|
||||
let recovery_key =
|
||||
data.backup_recovery_key.map(|k| RecoveryKey::from_base58(k.as_str())).transpose()?;
|
||||
Ok((sessions, inbound_group_sessions))
|
||||
}
|
||||
|
||||
let cross_signing = PrivateCrossSigningIdentity::empty((*user_id).into());
|
||||
runtime.block_on(cross_signing.import_secrets_unchecked(
|
||||
data.cross_signing.master_key.as_deref(),
|
||||
data.cross_signing.self_signing_key.as_deref(),
|
||||
data.cross_signing.user_signing_key.as_deref(),
|
||||
))?;
|
||||
/// Migrate room settings, including room algorithm and whether to block
|
||||
/// untrusted devices from legacy store to Sqlite store.
|
||||
///
|
||||
/// Note that this method should only be used if a client has already migrated
|
||||
/// account data via [migrate](#method.migrate) method, which did not include
|
||||
/// room settings. For a brand new migration, the [migrate](#method.migrate)
|
||||
/// method will take care of room settings automatically, if provided.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_settings` - Map of room settings
|
||||
///
|
||||
/// * `path` - The path where the Sqlite store should be created.
|
||||
///
|
||||
/// * `passphrase` - The passphrase that should be used to encrypt the data at
|
||||
/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store
|
||||
/// and all its data will remain unencrypted.
|
||||
pub fn migrate_room_settings(
|
||||
room_settings: HashMap<String, RoomSettings>,
|
||||
path: &str,
|
||||
passphrase: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let runtime = Runtime::new()?;
|
||||
runtime.block_on(async move {
|
||||
let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?;
|
||||
|
||||
data.cross_signing.master_key.zeroize();
|
||||
data.cross_signing.self_signing_key.zeroize();
|
||||
data.cross_signing.user_signing_key.zeroize();
|
||||
let mut rust_settings = HashMap::new();
|
||||
for (room_id, settings) in room_settings {
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
rust_settings.insert(room_id, settings.into());
|
||||
}
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
let changes = Changes { room_settings: rust_settings, ..Default::default() };
|
||||
store.save_changes(changes).await?;
|
||||
|
||||
let tracked_users: Vec<_> = data
|
||||
.tracked_users
|
||||
.into_iter()
|
||||
.map(|u| Ok(((parse_user_id(&u)?), true)))
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
|
||||
let tracked_users: Vec<_> = tracked_users.iter().map(|(u, d)| (&**u, *d)).collect();
|
||||
|
||||
runtime.block_on(store.save_tracked_users(tracked_users.as_slice()))?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
let changes = RustChanges {
|
||||
account: Some(account),
|
||||
private_identity: Some(cross_signing),
|
||||
sessions,
|
||||
inbound_group_sessions,
|
||||
recovery_key,
|
||||
backup_version: data.backup_version,
|
||||
..Default::default()
|
||||
};
|
||||
runtime.block_on(store.save_changes(changes))?;
|
||||
|
||||
processed_steps += 1;
|
||||
listener(processed_steps, total_steps);
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Callback that will be passed over the FFI to report progress
|
||||
@@ -344,7 +535,113 @@ impl<T: Fn(i32, i32)> ProgressListener for T {
|
||||
}
|
||||
}
|
||||
|
||||
/// An encryption algorithm to be used to encrypt messages sent to a room.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum EventEncryptionAlgorithm {
|
||||
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
|
||||
OlmV1Curve25519AesSha2,
|
||||
/// Megolm version 1 using AES-256 and SHA-256.
|
||||
MegolmV1AesSha2,
|
||||
}
|
||||
|
||||
impl From<EventEncryptionAlgorithm> for RustEventEncryptionAlgorithm {
|
||||
fn from(a: EventEncryptionAlgorithm) -> Self {
|
||||
match a {
|
||||
EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
|
||||
EventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RustEventEncryptionAlgorithm> for EventEncryptionAlgorithm {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: RustEventEncryptionAlgorithm) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => {
|
||||
Ok(Self::OlmV1Curve25519AesSha2)
|
||||
}
|
||||
RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(Self::MegolmV1AesSha2),
|
||||
_ => Err(serde::de::Error::custom(format!("Unsupported algorithm {value}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Who can see a room's history.
|
||||
pub enum HistoryVisibility {
|
||||
/// Previous events are accessible to newly joined members from the point
|
||||
/// they were invited onwards.
|
||||
///
|
||||
/// Events stop being accessible when the member's state changes to
|
||||
/// something other than *invite* or *join*.
|
||||
Invited,
|
||||
|
||||
/// Previous events are accessible to newly joined members from the point
|
||||
/// they joined the room onwards.
|
||||
/// Events stop being accessible when the member's state changes to
|
||||
/// something other than *join*.
|
||||
Joined,
|
||||
|
||||
/// Previous events are always accessible to newly joined members.
|
||||
///
|
||||
/// All events in the room are accessible, even those sent when the member
|
||||
/// was not a part of the room.
|
||||
Shared,
|
||||
|
||||
/// All events while this is the `HistoryVisibility` value may be shared by
|
||||
/// any participating homeserver with anyone, regardless of whether they
|
||||
/// have ever joined the room.
|
||||
WorldReadable,
|
||||
}
|
||||
|
||||
impl From<HistoryVisibility> for RustHistoryVisibility {
|
||||
fn from(h: HistoryVisibility) -> Self {
|
||||
match h {
|
||||
HistoryVisibility::Invited => Self::Invited,
|
||||
HistoryVisibility::Joined => Self::Joined,
|
||||
HistoryVisibility::Shared => Self::Shared,
|
||||
HistoryVisibility::WorldReadable => Self::Shared,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings that should be used when a room key is shared.
|
||||
///
|
||||
/// These settings control which algorithm the room key should use, how long a
|
||||
/// room key should be used and some other important information that determines
|
||||
/// the lifetime of a room key.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct EncryptionSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EventEncryptionAlgorithm,
|
||||
/// How long can the room key be used before it should be rotated. Time in
|
||||
/// seconds.
|
||||
pub rotation_period: u64,
|
||||
/// How many messages should be sent before the room key should be rotated.
|
||||
pub rotation_period_msgs: u64,
|
||||
/// The current history visibility of the room. The visibility will be
|
||||
/// tracked by the room key and the key will be rotated if the visibility
|
||||
/// changes.
|
||||
pub history_visibility: HistoryVisibility,
|
||||
/// Should untrusted devices receive the room key, or should they be
|
||||
/// excluded from the conversation.
|
||||
pub only_allow_trusted_devices: bool,
|
||||
}
|
||||
|
||||
impl From<EncryptionSettings> for RustEncryptionSettings {
|
||||
fn from(v: EncryptionSettings) -> Self {
|
||||
RustEncryptionSettings {
|
||||
algorithm: v.algorithm.into(),
|
||||
rotation_period: Duration::from_secs(v.rotation_period),
|
||||
rotation_period_msgs: v.rotation_period_msgs,
|
||||
history_visibility: v.history_visibility.into(),
|
||||
only_allow_trusted_devices: v.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that was successfully decrypted.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct DecryptedEvent {
|
||||
/// The decrypted version of the event.
|
||||
pub clear_event: String,
|
||||
@@ -356,11 +653,53 @@ pub struct DecryptedEvent {
|
||||
/// key to us. Is empty if the key came directly from the sender of the
|
||||
/// event.
|
||||
pub forwarding_curve25519_chain: Vec<String>,
|
||||
/// The shield state (color and message to display to user) for the event,
|
||||
/// representing the event's authenticity. Computed from the properties of
|
||||
/// the sender user identity and their Olm device.
|
||||
///
|
||||
/// Note that this is computed at time of decryption, so the value reflects
|
||||
/// the computed event authenticity at that time. Authenticity-related
|
||||
/// properties can change later on, such as when a user identity is
|
||||
/// subsequently verified or a device is deleted.
|
||||
pub shield_state: ShieldState,
|
||||
}
|
||||
|
||||
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
|
||||
/// for more info.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum ShieldColor {
|
||||
Red,
|
||||
Grey,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
|
||||
/// for more info.
|
||||
#[derive(uniffi::Record)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct ShieldState {
|
||||
color: ShieldColor,
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
impl From<RustShieldState> for ShieldState {
|
||||
fn from(value: RustShieldState) -> Self {
|
||||
match value {
|
||||
RustShieldState::Red { message } => {
|
||||
Self { color: ShieldColor::Red, message: Some(message.to_owned()) }
|
||||
}
|
||||
RustShieldState::Grey { message } => {
|
||||
Self { color: ShieldColor::Grey, message: Some(message.to_owned()) }
|
||||
}
|
||||
RustShieldState::None => Self { color: ShieldColor::None, message: None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing the state of our private cross signing keys, it shows
|
||||
/// which private cross signing keys we have locally stored.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct CrossSigningStatus {
|
||||
/// Do we have the master key.
|
||||
pub has_master: bool,
|
||||
@@ -385,6 +724,7 @@ pub struct CrossSigningKeyExport {
|
||||
}
|
||||
|
||||
/// Struct holding the number of room keys we have.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct RoomKeyCounts {
|
||||
/// The total number of room keys.
|
||||
pub total: i64,
|
||||
@@ -393,6 +733,7 @@ pub struct RoomKeyCounts {
|
||||
}
|
||||
|
||||
/// Backup keys and information we load from the store.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct BackupKeys {
|
||||
/// The recovery key as a base64 encoded string.
|
||||
recovery_key: Arc<BackupRecoveryKey>,
|
||||
@@ -400,6 +741,7 @@ pub struct BackupKeys {
|
||||
backup_version: String,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl BackupKeys {
|
||||
/// Get the recovery key that we're holding on to.
|
||||
pub fn recovery_key(&self) -> Arc<BackupRecoveryKey> {
|
||||
@@ -463,17 +805,65 @@ impl From<matrix_sdk_crypto::CrossSigningStatus> for CrossSigningStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Room encryption settings which are modified by state events or user options
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct RoomSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EventEncryptionAlgorithm,
|
||||
/// Should untrusted devices receive the room key, or should they be
|
||||
/// excluded from the conversation.
|
||||
pub only_allow_trusted_devices: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<RustRoomSettings> for RoomSettings {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: RustRoomSettings) -> Result<Self, Self::Error> {
|
||||
let algorithm = value.algorithm.try_into()?;
|
||||
Ok(Self { algorithm, only_allow_trusted_devices: value.only_allow_trusted_devices })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoomSettings> for RustRoomSettings {
|
||||
fn from(value: RoomSettings) -> Self {
|
||||
Self {
|
||||
algorithm: value.algorithm.into(),
|
||||
only_allow_trusted_devices: value.only_allow_trusted_devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_user_id(user_id: &str) -> Result<OwnedUserId, CryptoStoreError> {
|
||||
ruma::UserId::parse(user_id).map_err(|e| CryptoStoreError::InvalidUserId(user_id.to_owned(), e))
|
||||
}
|
||||
|
||||
#[allow(warnings)]
|
||||
mod generated {
|
||||
use super::*;
|
||||
include!(concat!(env!("OUT_DIR"), "/olm.uniffi.rs"));
|
||||
#[uniffi::export]
|
||||
fn version() -> String {
|
||||
matrix_sdk_crypto::VERSION.to_owned()
|
||||
}
|
||||
|
||||
pub use generated::*;
|
||||
#[uniffi::export]
|
||||
fn vodozemac_version() -> String {
|
||||
vodozemac::VERSION.to_owned()
|
||||
}
|
||||
|
||||
uniffi::include_scaffolding!("olm");
|
||||
|
||||
mod uniffi_types {
|
||||
pub use crate::{
|
||||
backup_recovery_key::{
|
||||
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
|
||||
},
|
||||
error::{CryptoStoreError, DecryptionError, SecretImportError},
|
||||
machine::{KeyRequestPair, OlmMachine},
|
||||
responses::{BootstrapCrossSigningResult, DeviceLists, Request},
|
||||
verification::{
|
||||
RequestVerificationResult, StartSasResult, Verification, VerificationRequest,
|
||||
},
|
||||
BackupKeys, CrossSigningKeyExport, CrossSigningStatus, DecryptedEvent, EncryptionSettings,
|
||||
EventEncryptionAlgorithm, RoomKeyCounts, RoomSettings, ShieldColor, ShieldState,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
@@ -482,7 +872,7 @@ mod test {
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::MigrationData;
|
||||
use crate::{migrate, OlmMachine};
|
||||
use crate::{migrate, EventEncryptionAlgorithm, OlmMachine, RoomSettings};
|
||||
|
||||
#[test]
|
||||
fn android_migration() -> Result<()> {
|
||||
@@ -565,7 +955,17 @@ mod test {
|
||||
"@this-is-me:matrix.org",
|
||||
"@Amandine:matrix.org",
|
||||
"@ganfra:matrix.org"
|
||||
]
|
||||
],
|
||||
"room_settings": {
|
||||
"!AZkqtjvtwPAuyNOXEt:matrix.org": {
|
||||
"algorithm": "OlmV1Curve25519AesSha2",
|
||||
"only_allow_trusted_devices": true
|
||||
},
|
||||
"!CWLUCoEWXSFyTCOtfL:matrix.org": {
|
||||
"algorithm": "MegolmV1AesSha2",
|
||||
"only_allow_trusted_devices": false
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
let migration_data: MigrationData = serde_json::from_value(data)?;
|
||||
@@ -594,6 +994,27 @@ mod test {
|
||||
let backup_keys = machine.get_backup_keys()?;
|
||||
assert!(backup_keys.is_some());
|
||||
|
||||
let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org".into())?;
|
||||
assert_eq!(
|
||||
Some(RoomSettings {
|
||||
algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2,
|
||||
only_allow_trusted_devices: true
|
||||
}),
|
||||
settings1
|
||||
);
|
||||
|
||||
let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org".into())?;
|
||||
assert_eq!(
|
||||
Some(RoomSettings {
|
||||
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
only_allow_trusted_devices: false
|
||||
}),
|
||||
settings2
|
||||
);
|
||||
|
||||
let settings3 = machine.get_room_settings("!XYZ:matrix.org".into())?;
|
||||
assert!(settings3.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,18 @@ pub struct LoggerWrapper {
|
||||
pub fn set_logger(logger: Box<dyn Logger>) {
|
||||
let logger = LoggerWrapper { inner: Arc::new(Mutex::new(logger)) };
|
||||
|
||||
let filter = EnvFilter::from_default_env().add_directive(
|
||||
"matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"),
|
||||
);
|
||||
let filter = EnvFilter::from_default_env()
|
||||
.add_directive(
|
||||
"matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"),
|
||||
)
|
||||
.add_directive(
|
||||
"matrix_sdk_sqlite=debug".parse().expect("Can't parse logging filter directive"),
|
||||
);
|
||||
|
||||
let _ = tracing_subscriber::fmt()
|
||||
.with_writer(logger)
|
||||
.with_env_filter(filter)
|
||||
.with_ansi(false)
|
||||
.without_time()
|
||||
.try_init();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
namespace olm {
|
||||
namespace matrix_sdk_crypto_ffi {
|
||||
void set_logger(Logger logger);
|
||||
[Throws=MigrationError]
|
||||
void migrate(
|
||||
@@ -7,26 +7,34 @@ namespace olm {
|
||||
string? passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=MigrationError]
|
||||
void migrate_sessions(
|
||||
SessionMigrationData data,
|
||||
[ByRef] string path,
|
||||
string? passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=MigrationError]
|
||||
void migrate_room_settings(
|
||||
record<string, RoomSettings> room_settings,
|
||||
[ByRef] string path,
|
||||
string? passphrase
|
||||
);
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface MigrationError {
|
||||
Generic(string error_message);
|
||||
Generic(string error_message);
|
||||
};
|
||||
|
||||
callback interface Logger {
|
||||
void log(string logLine);
|
||||
void log(string log_line);
|
||||
};
|
||||
|
||||
callback interface ProgressListener {
|
||||
void on_progress(i32 progress, i32 total);
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum PkDecryptionError {
|
||||
"Olm",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum KeyImportError {
|
||||
"Export",
|
||||
@@ -52,23 +60,21 @@ enum SecretImportError {
|
||||
|
||||
[Error]
|
||||
enum CryptoStoreError {
|
||||
"OpenStore",
|
||||
"CryptoStore",
|
||||
"OlmError",
|
||||
"Serialization",
|
||||
"Identifier",
|
||||
"InvalidUserId",
|
||||
"Identifier",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum DecryptionError {
|
||||
"Identifier",
|
||||
"Serialization",
|
||||
"Megolm",
|
||||
};
|
||||
|
||||
dictionary DeviceLists {
|
||||
sequence<string> changed;
|
||||
sequence<string> left;
|
||||
interface DecryptionError {
|
||||
Identifier(string error);
|
||||
Serialization(string error);
|
||||
Megolm(string error);
|
||||
MissingRoomKey(string error, string? withheld_code);
|
||||
Store(string error);
|
||||
};
|
||||
|
||||
dictionary KeysImportResult {
|
||||
@@ -77,13 +83,6 @@ dictionary KeysImportResult {
|
||||
record<DOMString, record<DOMString, sequence<string>>> keys;
|
||||
};
|
||||
|
||||
dictionary DecryptedEvent {
|
||||
string clear_event;
|
||||
string sender_curve25519_key;
|
||||
string? claimed_ed25519_key;
|
||||
sequence<string> forwarding_curve25519_chain;
|
||||
};
|
||||
|
||||
dictionary Device {
|
||||
string user_id;
|
||||
string device_id;
|
||||
@@ -111,12 +110,6 @@ interface UserIdentity {
|
||||
);
|
||||
};
|
||||
|
||||
dictionary CrossSigningStatus {
|
||||
boolean has_master;
|
||||
boolean has_self_signing;
|
||||
boolean has_user_signing;
|
||||
};
|
||||
|
||||
dictionary CrossSigningKeyExport {
|
||||
string? master_key;
|
||||
string? self_signing_key;
|
||||
@@ -145,19 +138,38 @@ dictionary StartSasResult {
|
||||
OutgoingVerificationRequest request;
|
||||
};
|
||||
|
||||
dictionary Sas {
|
||||
string other_user_id;
|
||||
string other_device_id;
|
||||
string flow_id;
|
||||
string? room_id;
|
||||
boolean we_started;
|
||||
boolean has_been_accepted;
|
||||
boolean can_be_presented;
|
||||
boolean supports_emoji;
|
||||
boolean have_we_confirmed;
|
||||
boolean is_done;
|
||||
boolean is_cancelled;
|
||||
CancelInfo? cancel_info;
|
||||
interface Sas {
|
||||
string other_user_id();
|
||||
string other_device_id();
|
||||
string flow_id();
|
||||
string? room_id();
|
||||
boolean we_started();
|
||||
boolean is_done();
|
||||
|
||||
OutgoingVerificationRequest? accept();
|
||||
[Throws=CryptoStoreError]
|
||||
ConfirmVerificationResult? confirm();
|
||||
OutgoingVerificationRequest? cancel([ByRef] string cancel_code);
|
||||
|
||||
sequence<i32>? get_emoji_indices();
|
||||
sequence<i32>? get_decimals();
|
||||
|
||||
void set_changes_listener(SasListener listener);
|
||||
SasState state();
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface SasState {
|
||||
Started();
|
||||
Accepted();
|
||||
KeysExchanged(sequence<i32>? emojis, sequence<i32> decimals);
|
||||
Confirmed();
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
};
|
||||
|
||||
callback interface SasListener {
|
||||
void on_change(SasState state);
|
||||
};
|
||||
|
||||
dictionary ScanResult {
|
||||
@@ -165,34 +177,81 @@ dictionary ScanResult {
|
||||
OutgoingVerificationRequest request;
|
||||
};
|
||||
|
||||
dictionary QrCode {
|
||||
string other_user_id;
|
||||
string other_device_id;
|
||||
string flow_id;
|
||||
string? room_id;
|
||||
boolean we_started;
|
||||
boolean other_side_scanned;
|
||||
boolean has_been_confirmed;
|
||||
boolean reciprocated;
|
||||
boolean is_done;
|
||||
boolean is_cancelled;
|
||||
CancelInfo? cancel_info;
|
||||
interface QrCode {
|
||||
string other_user_id();
|
||||
string other_device_id();
|
||||
string flow_id();
|
||||
string? room_id();
|
||||
boolean we_started();
|
||||
boolean is_done();
|
||||
boolean is_cancelled();
|
||||
CancelInfo? cancel_info();
|
||||
|
||||
boolean reciprocated();
|
||||
boolean has_been_scanned();
|
||||
|
||||
ConfirmVerificationResult? confirm();
|
||||
OutgoingVerificationRequest? cancel([ByRef] string cancel_code);
|
||||
string? generate_qr_code();
|
||||
|
||||
void set_changes_listener(QrCodeListener listener);
|
||||
QrCodeState state();
|
||||
};
|
||||
|
||||
dictionary VerificationRequest {
|
||||
string other_user_id;
|
||||
string? other_device_id;
|
||||
string flow_id;
|
||||
string? room_id;
|
||||
boolean we_started;
|
||||
boolean is_ready;
|
||||
boolean is_passive;
|
||||
boolean is_done;
|
||||
boolean is_cancelled;
|
||||
CancelInfo? cancel_info;
|
||||
sequence<string>? their_methods;
|
||||
sequence<string>? our_methods;
|
||||
[Enum]
|
||||
interface QrCodeState {
|
||||
Started();
|
||||
Scanned();
|
||||
Confirmed();
|
||||
Reciprocated();
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
};
|
||||
|
||||
callback interface QrCodeListener {
|
||||
void on_change(QrCodeState state);
|
||||
};
|
||||
|
||||
interface VerificationRequest {
|
||||
string other_user_id();
|
||||
string? other_device_id();
|
||||
string flow_id();
|
||||
string? room_id();
|
||||
boolean we_started();
|
||||
boolean is_ready();
|
||||
boolean is_done();
|
||||
boolean is_passive();
|
||||
boolean is_cancelled();
|
||||
CancelInfo? cancel_info();
|
||||
|
||||
sequence<string>? their_supported_methods();
|
||||
sequence<string>? our_supported_methods();
|
||||
|
||||
OutgoingVerificationRequest? accept(sequence<string> methods);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
StartSasResult? start_sas_verification();
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
QrCode? start_qr_verification();
|
||||
ScanResult? scan_qr_code([ByRef] string data);
|
||||
|
||||
OutgoingVerificationRequest? cancel();
|
||||
|
||||
void set_changes_listener(VerificationRequestListener listener);
|
||||
VerificationRequestState state();
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface VerificationRequestState {
|
||||
Requested();
|
||||
Ready(sequence<string> their_methods, sequence<string> our_methods);
|
||||
Done();
|
||||
Cancelled(CancelInfo cancel_info);
|
||||
};
|
||||
|
||||
callback interface VerificationRequestListener {
|
||||
void on_change(VerificationRequestState state);
|
||||
};
|
||||
|
||||
dictionary RequestVerificationResult {
|
||||
@@ -205,10 +264,9 @@ dictionary ConfirmVerificationResult {
|
||||
SignatureUploadRequest? signature_request;
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface Verification {
|
||||
SasV1(Sas sas);
|
||||
QrCodeV1(QrCode qrcode);
|
||||
QrCode? as_qr();
|
||||
Sas? as_sas();
|
||||
};
|
||||
|
||||
dictionary KeyRequestPair {
|
||||
@@ -247,6 +305,26 @@ enum RequestType {
|
||||
"RoomMessage",
|
||||
};
|
||||
|
||||
enum LocalTrust {
|
||||
"Verified",
|
||||
"BlackListed",
|
||||
"Ignored",
|
||||
"Unset",
|
||||
};
|
||||
|
||||
|
||||
enum EventEncryptionAlgorithm {
|
||||
"OlmV1Curve25519AesSha2",
|
||||
"MegolmV1AesSha2",
|
||||
};
|
||||
|
||||
enum HistoryVisibility {
|
||||
"Invited",
|
||||
"Joined",
|
||||
"Shared",
|
||||
"WorldReadable",
|
||||
};
|
||||
|
||||
interface OlmMachine {
|
||||
[Throws=CryptoStoreError]
|
||||
constructor(
|
||||
@@ -256,15 +334,6 @@ interface OlmMachine {
|
||||
string? passphrase
|
||||
);
|
||||
|
||||
record<DOMString, string> identity_keys();
|
||||
string user_id();
|
||||
string device_id();
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
string receive_sync_changes([ByRef] string events,
|
||||
DeviceLists device_changes,
|
||||
record<DOMString, i32> key_counts,
|
||||
sequence<string>? unused_fallback_keys);
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Request> outgoing_requests();
|
||||
[Throws=CryptoStoreError]
|
||||
@@ -274,11 +343,6 @@ interface OlmMachine {
|
||||
[ByRef] string response
|
||||
);
|
||||
|
||||
[Throws=DecryptionError]
|
||||
DecryptedEvent decrypt_room_event([ByRef] string event, [ByRef] string room_id);
|
||||
[Throws=CryptoStoreError]
|
||||
string encrypt([ByRef] string room_id, [ByRef] string event_type, [ByRef] string content);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
UserIdentity? get_identity([ByRef] string user_id, u32 timeout);
|
||||
[Throws=SignatureError]
|
||||
@@ -286,79 +350,12 @@ interface OlmMachine {
|
||||
[Throws=CryptoStoreError]
|
||||
Device? get_device([ByRef] string user_id, [ByRef] string device_id, u32 timeout);
|
||||
[Throws=CryptoStoreError]
|
||||
void mark_device_as_trusted([ByRef] string user_id, [ByRef] string device_id);
|
||||
void set_local_trust([ByRef] string user_id, [ByRef] string device_id, LocalTrust trust_state);
|
||||
[Throws=SignatureError]
|
||||
SignatureUploadRequest verify_device([ByRef] string user_id, [ByRef] string device_id);
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Device> get_user_devices([ByRef] string user_id, u32 timeout);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
boolean is_user_tracked([ByRef] string user_id);
|
||||
void update_tracked_users(sequence<string> users);
|
||||
[Throws=CryptoStoreError]
|
||||
Request? get_missing_sessions(sequence<string> users);
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Request> share_room_key([ByRef] string room_id, sequence<string> users);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
void receive_unencrypted_verification_event([ByRef] string event, [ByRef] string room_id);
|
||||
sequence<VerificationRequest> get_verification_requests([ByRef] string user_id);
|
||||
VerificationRequest? get_verification_request([ByRef] string user_id, [ByRef] string flow_id);
|
||||
Verification? get_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
VerificationRequest? request_verification(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string room_id,
|
||||
[ByRef] string event_id,
|
||||
sequence<string> methods
|
||||
);
|
||||
[Throws=CryptoStoreError]
|
||||
string? verification_request_content(
|
||||
[ByRef] string user_id,
|
||||
sequence<string> methods
|
||||
);
|
||||
[Throws=CryptoStoreError]
|
||||
RequestVerificationResult? request_self_verification(sequence<string> methods);
|
||||
[Throws=CryptoStoreError]
|
||||
RequestVerificationResult? request_verification_with_device(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string device_id,
|
||||
sequence<string> methods
|
||||
);
|
||||
|
||||
OutgoingVerificationRequest? accept_verification_request(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string flow_id,
|
||||
sequence<string> methods
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
ConfirmVerificationResult? confirm_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
OutgoingVerificationRequest? cancel_verification(
|
||||
[ByRef] string user_id,
|
||||
[ByRef] string flow_id,
|
||||
[ByRef] string cancel_code
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
StartSasResult? start_sas_with_device([ByRef] string user_id, [ByRef] string device_id);
|
||||
[Throws=CryptoStoreError]
|
||||
StartSasResult? start_sas_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
OutgoingVerificationRequest? accept_sas_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
sequence<i32>? get_emoji_index([ByRef] string user_id, [ByRef] string flow_id);
|
||||
sequence<i32>? get_decimals([ByRef] string user_id, [ByRef] string flow_id);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
QrCode? start_qr_verification([ByRef] string user_id, [ByRef] string flow_id);
|
||||
ScanResult? scan_qr_code([ByRef] string user_id, [ByRef] string flow_id, [ByRef] string data);
|
||||
string? generate_qr_code([ByRef] string user_id, [ByRef] string flow_id);
|
||||
|
||||
[Throws=DecryptionError]
|
||||
KeyRequestPair request_room_key([ByRef] string event, [ByRef] string room_id);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
string export_room_keys([ByRef] string passphrase, i32 rounds);
|
||||
[Throws=KeyImportError]
|
||||
KeysImportResult import_room_keys(
|
||||
[ByRef] string keys,
|
||||
@@ -370,34 +367,13 @@ interface OlmMachine {
|
||||
[ByRef] string keys,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=CryptoStoreError]
|
||||
void discard_room_key([ByRef] string room_id);
|
||||
|
||||
CrossSigningStatus cross_signing_status();
|
||||
[Throws=CryptoStoreError]
|
||||
BootstrapCrossSigningResult bootstrap_cross_signing();
|
||||
CrossSigningKeyExport? export_cross_signing_keys();
|
||||
[Throws=SecretImportError]
|
||||
void import_cross_signing_keys(CrossSigningKeyExport export);
|
||||
[Throws=CryptoStoreError]
|
||||
boolean is_identity_verified([ByRef] string user_id);
|
||||
|
||||
record<DOMString, record<DOMString, string>> sign([ByRef] string message);
|
||||
[Throws=DecodeError]
|
||||
void enable_backup_v1(MegolmV1BackupKey key, string version);
|
||||
[Throws=CryptoStoreError]
|
||||
void disable_backup();
|
||||
[Throws=CryptoStoreError]
|
||||
Request? backup_room_keys();
|
||||
[Throws=CryptoStoreError]
|
||||
void save_recovery_key(BackupRecoveryKey? key, string? version);
|
||||
[Throws=CryptoStoreError]
|
||||
RoomKeyCounts room_key_counts();
|
||||
[Throws=CryptoStoreError]
|
||||
BackupKeys? get_backup_keys();
|
||||
boolean backup_enabled();
|
||||
[Throws=CryptoStoreError]
|
||||
boolean verify_backup([ByRef] string auth_data);
|
||||
SignatureVerification verify_backup([ByRef] string auth_data);
|
||||
};
|
||||
|
||||
dictionary PassphraseInfo {
|
||||
@@ -405,21 +381,18 @@ dictionary PassphraseInfo {
|
||||
i32 private_key_iterations;
|
||||
};
|
||||
|
||||
dictionary MegolmV1BackupKey {
|
||||
string public_key;
|
||||
record<DOMString, record<DOMString, string>> signatures;
|
||||
PassphraseInfo? passphrase_info;
|
||||
string backup_algorithm;
|
||||
dictionary SignatureVerification {
|
||||
SignatureState device_signature;
|
||||
SignatureState user_identity_signature;
|
||||
record<DOMString, SignatureState> other_devices_signatures;
|
||||
boolean trusted;
|
||||
};
|
||||
|
||||
interface BackupKeys {
|
||||
BackupRecoveryKey recovery_key();
|
||||
string backup_version();
|
||||
};
|
||||
|
||||
dictionary RoomKeyCounts {
|
||||
i64 total;
|
||||
i64 backed_up;
|
||||
enum SignatureState {
|
||||
"Missing",
|
||||
"Invalid",
|
||||
"ValidButNotTrusted",
|
||||
"ValidAndTrusted",
|
||||
};
|
||||
|
||||
[Error]
|
||||
@@ -438,11 +411,6 @@ interface BackupRecoveryKey {
|
||||
constructor(string key);
|
||||
[Name=from_base58, Throws=DecodeError]
|
||||
constructor(string key);
|
||||
string to_base58();
|
||||
string to_base64();
|
||||
MegolmV1BackupKey megolm_v1_public_key();
|
||||
[Throws=PkDecryptionError]
|
||||
string decrypt_v1(string ephemeral_key, string mac, string ciphertext);
|
||||
};
|
||||
|
||||
dictionary MigrationData {
|
||||
@@ -454,6 +422,17 @@ dictionary MigrationData {
|
||||
sequence<u8> pickle_key;
|
||||
CrossSigningKeyExport cross_signing;
|
||||
sequence<string> tracked_users;
|
||||
record<string, RoomSettings> room_settings;
|
||||
};
|
||||
|
||||
dictionary SessionMigrationData {
|
||||
string user_id;
|
||||
string device_id;
|
||||
string curve25519_key;
|
||||
string ed25519_key;
|
||||
sequence<PickledSession> sessions;
|
||||
sequence<PickledInboundGroupSession> inbound_group_sessions;
|
||||
sequence<u8> pickle_key;
|
||||
};
|
||||
|
||||
dictionary PickledAccount {
|
||||
@@ -481,3 +460,8 @@ dictionary PickledInboundGroupSession {
|
||||
boolean imported;
|
||||
boolean backed_up;
|
||||
};
|
||||
|
||||
dictionary RoomSettings {
|
||||
EventEncryptionAlgorithm algorithm;
|
||||
boolean only_allow_trusted_devices;
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ use ruma::{
|
||||
},
|
||||
},
|
||||
message::send_message_event::v3::Response as RoomMessageResponse,
|
||||
sync::sync_events::v3::DeviceLists as RumaDeviceLists,
|
||||
sync::sync_events::DeviceLists as RumaDeviceLists,
|
||||
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
|
||||
},
|
||||
assign,
|
||||
@@ -118,9 +118,9 @@ pub enum Request {
|
||||
KeysUpload { request_id: String, body: String },
|
||||
KeysQuery { request_id: String, users: Vec<String> },
|
||||
KeysClaim { request_id: String, one_time_keys: HashMap<String, HashMap<String, String>> },
|
||||
KeysBackup { request_id: String, version: String, rooms: String },
|
||||
RoomMessage { request_id: String, room_id: String, event_type: String, content: String },
|
||||
SignatureUpload { request_id: String, body: String },
|
||||
KeysBackup { request_id: String, version: String, rooms: String },
|
||||
}
|
||||
|
||||
impl From<OutgoingRequest> for Request {
|
||||
@@ -231,6 +231,7 @@ pub enum RequestType {
|
||||
RoomMessage,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct DeviceLists {
|
||||
pub changed: Vec<String>,
|
||||
pub left: Vec<String>,
|
||||
|
||||
@@ -1,100 +1,448 @@
|
||||
use matrix_sdk_crypto::{
|
||||
CancelInfo as RustCancelInfo, QrVerification as InnerQr, Sas as InnerSas,
|
||||
VerificationRequest as InnerVerificationRequest,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{OutgoingVerificationRequest, SignatureUploadRequest};
|
||||
use base64::{
|
||||
alphabet,
|
||||
engine::{general_purpose, GeneralPurpose},
|
||||
Engine,
|
||||
};
|
||||
use futures_util::{Stream, StreamExt};
|
||||
use matrix_sdk_crypto::{
|
||||
matrix_sdk_qrcode::QrVerificationData, CancelInfo as RustCancelInfo, QrVerification as InnerQr,
|
||||
QrVerificationState, Sas as InnerSas, SasState as RustSasState,
|
||||
Verification as InnerVerification, VerificationRequest as InnerVerificationRequest,
|
||||
VerificationRequestState as RustVerificationRequestState,
|
||||
};
|
||||
use ruma::events::key::verification::VerificationMethod;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest};
|
||||
|
||||
const STANDARD_NO_PAD: GeneralPurpose =
|
||||
GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
|
||||
|
||||
/// Listener that will be passed over the FFI to report changes to a SAS
|
||||
/// verification.
|
||||
pub trait SasListener: Send {
|
||||
/// The callback that should be called on the Rust side
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - The current state of the SAS verification.
|
||||
fn on_change(&self, state: SasState);
|
||||
}
|
||||
|
||||
/// An Enum describing the state the SAS verification is in.
|
||||
pub enum SasState {
|
||||
/// The verification has been started, the protocols that should be used
|
||||
/// have been proposed and can be accepted.
|
||||
Started,
|
||||
/// The verification has been accepted and both sides agreed to a set of
|
||||
/// protocols that will be used for the verification process.
|
||||
Accepted,
|
||||
/// The public keys have been exchanged and the short auth string can be
|
||||
/// presented to the user.
|
||||
KeysExchanged {
|
||||
/// The emojis that represent the short auth string, will be `None` if
|
||||
/// the emoji SAS method wasn't one of accepted protocols.
|
||||
emojis: Option<Vec<i32>>,
|
||||
/// The list of decimals that represent the short auth string.
|
||||
decimals: Vec<i32>,
|
||||
},
|
||||
/// The verification process has been confirmed from our side, we're waiting
|
||||
/// for the other side to confirm as well.
|
||||
Confirmed,
|
||||
/// The verification process has been successfully concluded.
|
||||
Done,
|
||||
/// The verification process has been cancelled.
|
||||
Cancelled {
|
||||
/// Information about the reason of the cancellation.
|
||||
cancel_info: CancelInfo,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<RustSasState> for SasState {
|
||||
fn from(s: RustSasState) -> Self {
|
||||
match s {
|
||||
RustSasState::Started { .. } => Self::Started,
|
||||
RustSasState::Accepted { .. } => Self::Accepted,
|
||||
RustSasState::KeysExchanged { emojis, decimals } => Self::KeysExchanged {
|
||||
emojis: emojis.map(|e| e.indices.map(|i| i as i32).to_vec()),
|
||||
decimals: [decimals.0.into(), decimals.1.into(), decimals.2.into()].to_vec(),
|
||||
},
|
||||
RustSasState::Confirmed => Self::Confirmed,
|
||||
RustSasState::Done { .. } => Self::Done,
|
||||
RustSasState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum representing the different verification flows we support.
|
||||
pub enum Verification {
|
||||
/// The `m.sas.v1` verification flow.
|
||||
SasV1 {
|
||||
#[allow(missing_docs)]
|
||||
sas: Sas,
|
||||
},
|
||||
/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
|
||||
/// verification flow.
|
||||
QrCodeV1 {
|
||||
#[allow(missing_docs)]
|
||||
qrcode: QrCode,
|
||||
},
|
||||
pub struct Verification {
|
||||
pub(crate) inner: InnerVerification,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
impl Verification {
|
||||
/// Try to represent the `Verification` as an `Sas` verification object,
|
||||
/// returns `None` if the verification is not a `Sas` verification.
|
||||
pub fn as_sas(&self) -> Option<Arc<Sas>> {
|
||||
if let InnerVerification::SasV1(sas) = &self.inner {
|
||||
Some(Sas { inner: sas.to_owned(), runtime: self.runtime.to_owned() }.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to represent the `Verification` as an `QrCode` verification object,
|
||||
/// returns `None` if the verification is not a `QrCode` verification.
|
||||
pub fn as_qr(&self) -> Option<Arc<QrCode>> {
|
||||
if let InnerVerification::QrV1(qr) = &self.inner {
|
||||
Some(QrCode { inner: qr.to_owned(), runtime: self.runtime.to_owned() }.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `m.sas.v1` verification flow.
|
||||
pub struct Sas {
|
||||
/// The other user that is participating in the verification flow
|
||||
pub other_user_id: String,
|
||||
/// The other user's device that is participating in the verification flow
|
||||
pub other_device_id: String,
|
||||
/// The unique ID of this verification flow, will be a random string for
|
||||
/// to-device events or a event ID for in-room events.
|
||||
pub flow_id: String,
|
||||
/// The room ID where this verification is happening, will be `None` if the
|
||||
/// verification is going through to-device messages
|
||||
pub room_id: Option<String>,
|
||||
/// Did we initiate the verification flow
|
||||
pub we_started: bool,
|
||||
/// Has the non-initiating side accepted the verification flow
|
||||
pub has_been_accepted: bool,
|
||||
/// Can the short auth string be presented
|
||||
pub can_be_presented: bool,
|
||||
/// Does the flow support the emoji representation of the short auth string
|
||||
pub supports_emoji: bool,
|
||||
/// Have we confirmed that the short auth strings match
|
||||
pub have_we_confirmed: bool,
|
||||
/// Has the verification completed successfully
|
||||
pub is_done: bool,
|
||||
/// Has the flow been cancelled
|
||||
pub is_cancelled: bool,
|
||||
/// Information about the cancellation of the flow, will be `None` if the
|
||||
/// flow hasn't been cancelled
|
||||
pub cancel_info: Option<CancelInfo>,
|
||||
pub(crate) inner: InnerSas,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
impl Sas {
|
||||
/// Get the user id of the other side.
|
||||
pub fn other_user_id(&self) -> String {
|
||||
self.inner.other_user_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the device ID of the other side.
|
||||
pub fn other_device_id(&self) -> String {
|
||||
self.inner.other_device_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the unique ID that identifies this SAS verification flow.
|
||||
pub fn flow_id(&self) -> String {
|
||||
self.inner.flow_id().as_str().to_owned()
|
||||
}
|
||||
|
||||
/// Get the room id if the verification is happening inside a room.
|
||||
pub fn room_id(&self) -> Option<String> {
|
||||
self.inner.room_id().map(|r| r.to_string())
|
||||
}
|
||||
|
||||
/// Is the SAS flow done.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Accept that we're going forward with the short auth string verification.
|
||||
pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.accept().map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Confirm a verification was successful.
|
||||
///
|
||||
/// This method should be called if a short auth string should be confirmed
|
||||
/// as matching.
|
||||
pub fn confirm(&self) -> Result<Option<ConfirmVerificationResult>, CryptoStoreError> {
|
||||
let (requests, signature_request) = self.runtime.block_on(self.inner.confirm())?;
|
||||
|
||||
let requests = requests.into_iter().map(|r| r.into()).collect();
|
||||
|
||||
Ok(Some(ConfirmVerificationResult {
|
||||
requests,
|
||||
signature_request: signature_request.map(|s| s.into()),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Cancel the SAS verification using the given cancel code.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cancel_code` - The error code for why the verification was cancelled,
|
||||
/// manual cancellatio usually happens with `m.user` cancel code. The full
|
||||
/// list of cancel codes can be found in the [spec]
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
|
||||
pub fn cancel(&self, cancel_code: &str) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Get a list of emoji indices of the emoji representation of the short
|
||||
/// auth string.
|
||||
///
|
||||
/// *Note*: A SAS verification needs to be started and in the presentable
|
||||
/// state for this to return the list of emoji indices, otherwise returns
|
||||
/// `None`.
|
||||
pub fn get_emoji_indices(&self) -> Option<Vec<i32>> {
|
||||
self.inner.emoji_index().map(|v| v.iter().map(|i| (*i).into()).collect())
|
||||
}
|
||||
|
||||
/// Get the decimal representation of the short auth string.
|
||||
///
|
||||
/// *Note*: A SAS verification needs to be started and in the presentable
|
||||
/// state for this to return the list of decimals, otherwise returns
|
||||
/// `None`.
|
||||
pub fn get_decimals(&self) -> Option<Vec<i32>> {
|
||||
self.inner.decimals().map(|v| [v.0.into(), v.1.into(), v.2.into()].to_vec())
|
||||
}
|
||||
|
||||
/// Set a listener for changes in the SAS verification process.
|
||||
///
|
||||
/// The given callback will be called whenever the state changes.
|
||||
///
|
||||
/// This method can be used to react to changes in the state of the
|
||||
/// verification process, or rather the method can be used to handle
|
||||
/// each step of the verification process.
|
||||
///
|
||||
/// This method will spawn a tokio task on the Rust side, once we reach the
|
||||
/// Done or Cancelled state, the task will stop listening for changes.
|
||||
///
|
||||
/// # Flowchart
|
||||
///
|
||||
/// The flow of the verification process is pictured bellow. Please note
|
||||
/// that the process can be cancelled at each step of the process.
|
||||
/// Either side can cancel the process.
|
||||
///
|
||||
/// ```text
|
||||
/// ┌───────┐
|
||||
/// │Started│
|
||||
/// └───┬───┘
|
||||
/// │
|
||||
/// ┌────⌄───┐
|
||||
/// │Accepted│
|
||||
/// └────┬───┘
|
||||
/// │
|
||||
/// ┌───────⌄──────┐
|
||||
/// │Keys Exchanged│
|
||||
/// └───────┬──────┘
|
||||
/// │
|
||||
/// ________⌄________
|
||||
/// ╱ ╲ ┌─────────┐
|
||||
/// ╱ Does the short ╲______│Cancelled│
|
||||
/// ╲ auth string match ╱ no └─────────┘
|
||||
/// ╲_________________╱
|
||||
/// │yes
|
||||
/// │
|
||||
/// ┌────⌄────┐
|
||||
/// │Confirmed│
|
||||
/// └────┬────┘
|
||||
/// │
|
||||
/// ┌───⌄───┐
|
||||
/// │ Done │
|
||||
/// └───────┘
|
||||
/// ```
|
||||
pub fn set_changes_listener(&self, listener: Box<dyn SasListener>) {
|
||||
let stream = self.inner.changes();
|
||||
|
||||
self.runtime.spawn(Self::changes_listener(stream, listener));
|
||||
}
|
||||
|
||||
/// Get the current state of the SAS verification process.
|
||||
pub fn state(&self) -> SasState {
|
||||
self.inner.state().into()
|
||||
}
|
||||
|
||||
async fn changes_listener(
|
||||
mut stream: impl Stream<Item = RustSasState> + std::marker::Unpin,
|
||||
listener: Box<dyn SasListener>,
|
||||
) {
|
||||
while let Some(state) = stream.next().await {
|
||||
// If we receive a done or a cancelled state we're at the end of our road, we
|
||||
// break out of the loop to deallocate the stream and finish the
|
||||
// task.
|
||||
let should_break =
|
||||
matches!(state, RustSasState::Done { .. } | RustSasState::Cancelled { .. });
|
||||
|
||||
listener.on_change(state.into());
|
||||
|
||||
if should_break {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Listener that will be passed over the FFI to report changes to a QrCode
|
||||
/// verification.
|
||||
pub trait QrCodeListener: Send {
|
||||
/// The callback that should be called on the Rust side
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - The current state of the QrCode verification.
|
||||
fn on_change(&self, state: QrCodeState);
|
||||
}
|
||||
|
||||
/// An Enum describing the state the QrCode verification is in.
|
||||
pub enum QrCodeState {
|
||||
/// The QR verification has been started.
|
||||
Started,
|
||||
/// The QR verification has been scanned by the other side.
|
||||
Scanned,
|
||||
/// The scanning of the QR code has been confirmed by us.
|
||||
Confirmed,
|
||||
/// We have successfully scanned the QR code and are able to send a
|
||||
/// reciprocation event.
|
||||
Reciprocated,
|
||||
/// The verification process has been successfully concluded.
|
||||
Done,
|
||||
/// The verification process has been cancelled.
|
||||
Cancelled {
|
||||
/// Information about the reason of the cancellation.
|
||||
cancel_info: CancelInfo,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<QrVerificationState> for QrCodeState {
|
||||
fn from(value: QrVerificationState) -> Self {
|
||||
match value {
|
||||
QrVerificationState::Started => Self::Started,
|
||||
QrVerificationState::Scanned => Self::Scanned,
|
||||
QrVerificationState::Confirmed => Self::Confirmed,
|
||||
QrVerificationState::Reciprocated => Self::Reciprocated,
|
||||
QrVerificationState::Done { .. } => Self::Done,
|
||||
QrVerificationState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
|
||||
/// verification flow.
|
||||
pub struct QrCode {
|
||||
/// The other user that is participating in the verification flow
|
||||
pub other_user_id: String,
|
||||
/// The other user's device that is participating in the verification flow
|
||||
pub other_device_id: String,
|
||||
/// The unique ID of this verification flow, will be a random string for
|
||||
/// to-device events or a event ID for in-room events.
|
||||
pub flow_id: String,
|
||||
/// The room ID where this verification is happening, will be `None` if the
|
||||
/// verification is going through to-device messages
|
||||
pub room_id: Option<String>,
|
||||
/// Did we initiate the verification flow
|
||||
pub we_started: bool,
|
||||
/// Has the QR code been scanned by the other side
|
||||
pub other_side_scanned: bool,
|
||||
/// Has the scanning of the QR code been confirmed by us
|
||||
pub has_been_confirmed: bool,
|
||||
/// Did we scan the QR code and sent out a reciprocation
|
||||
pub reciprocated: bool,
|
||||
/// Has the verification completed successfully
|
||||
pub is_done: bool,
|
||||
/// Has the flow been cancelled
|
||||
pub is_cancelled: bool,
|
||||
/// Information about the cancellation of the flow, will be `None` if the
|
||||
/// flow hasn't been cancelled
|
||||
pub cancel_info: Option<CancelInfo>,
|
||||
pub(crate) inner: InnerQr,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
impl From<InnerQr> for QrCode {
|
||||
fn from(qr: InnerQr) -> Self {
|
||||
Self {
|
||||
other_user_id: qr.other_user_id().to_string(),
|
||||
flow_id: qr.flow_id().as_str().to_owned(),
|
||||
is_cancelled: qr.is_cancelled(),
|
||||
is_done: qr.is_done(),
|
||||
cancel_info: qr.cancel_info().map(|c| c.into()),
|
||||
reciprocated: qr.reciprocated(),
|
||||
we_started: qr.we_started(),
|
||||
other_side_scanned: qr.has_been_scanned(),
|
||||
has_been_confirmed: qr.has_been_confirmed(),
|
||||
other_device_id: qr.other_device_id().to_string(),
|
||||
room_id: qr.room_id().map(|r| r.to_string()),
|
||||
impl QrCode {
|
||||
/// Get the user id of the other side.
|
||||
pub fn other_user_id(&self) -> String {
|
||||
self.inner.other_user_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the device ID of the other side.
|
||||
pub fn other_device_id(&self) -> String {
|
||||
self.inner.other_device_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the unique ID that identifies this QR code verification flow.
|
||||
pub fn flow_id(&self) -> String {
|
||||
self.inner.flow_id().as_str().to_owned()
|
||||
}
|
||||
|
||||
/// Get the room id if the verification is happening inside a room.
|
||||
pub fn room_id(&self) -> Option<String> {
|
||||
self.inner.room_id().map(|r| r.to_string())
|
||||
}
|
||||
|
||||
/// Is the QR code verification done.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Has the verification flow been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.inner.is_cancelled()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Get the CancelInfo of this QR code verification object.
|
||||
///
|
||||
/// Will be `None` if the flow has not been cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
self.inner.cancel_info().map(|c| c.into())
|
||||
}
|
||||
|
||||
/// Has the QR verification been scanned by the other side.
|
||||
///
|
||||
/// When the verification object is in this state it's required that the
|
||||
/// user confirms that the other side has scanned the QR code.
|
||||
pub fn has_been_scanned(&self) -> bool {
|
||||
self.inner.has_been_scanned()
|
||||
}
|
||||
|
||||
/// Have we successfully scanned the QR code and are able to send a
|
||||
/// reciprocation event.
|
||||
pub fn reciprocated(&self) -> bool {
|
||||
self.inner.reciprocated()
|
||||
}
|
||||
|
||||
/// Cancel the QR code verification using the given cancel code.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cancel_code` - The error code for why the verification was cancelled,
|
||||
/// manual cancellatio usually happens with `m.user` cancel code. The full
|
||||
/// list of cancel codes can be found in the [spec]
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
|
||||
pub fn cancel(&self, cancel_code: &str) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Confirm a verification was successful.
|
||||
///
|
||||
/// This method should be called if we want to confirm that the other side
|
||||
/// has scanned our QR code.
|
||||
pub fn confirm(&self) -> Option<ConfirmVerificationResult> {
|
||||
self.inner.confirm_scanning().map(|r| ConfirmVerificationResult {
|
||||
requests: vec![r.into()],
|
||||
signature_request: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate data that should be encoded as a QR code.
|
||||
///
|
||||
/// This method should be called right before a QR code should be displayed,
|
||||
/// the returned data is base64 encoded (without padding) and needs to be
|
||||
/// decoded on the other side before it can be put through a QR code
|
||||
/// generator.
|
||||
pub fn generate_qr_code(&self) -> Option<String> {
|
||||
self.inner.to_bytes().map(|data| STANDARD_NO_PAD.encode(data)).ok()
|
||||
}
|
||||
|
||||
/// Set a listener for changes in the QrCode verification process.
|
||||
///
|
||||
/// The given callback will be called whenever the state changes.
|
||||
pub fn set_changes_listener(&self, listener: Box<dyn QrCodeListener>) {
|
||||
let stream = self.inner.changes();
|
||||
|
||||
self.runtime.spawn(Self::changes_listener(stream, listener));
|
||||
}
|
||||
|
||||
/// Get the current state of the QrCode verification process.
|
||||
pub fn state(&self) -> QrCodeState {
|
||||
self.inner.state().into()
|
||||
}
|
||||
|
||||
async fn changes_listener(
|
||||
mut stream: impl Stream<Item = QrVerificationState> + std::marker::Unpin,
|
||||
listener: Box<dyn QrCodeListener>,
|
||||
) {
|
||||
while let Some(state) = stream.next().await {
|
||||
// If we receive a done or a cancelled state we're at the end of our road, we
|
||||
// break out of the loop to deallocate the stream and finish the
|
||||
// task.
|
||||
let should_break = matches!(
|
||||
state,
|
||||
QrVerificationState::Done { .. } | QrVerificationState::Cancelled { .. }
|
||||
);
|
||||
|
||||
listener.on_change(state.into());
|
||||
|
||||
if should_break {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +470,7 @@ impl From<RustCancelInfo> for CancelInfo {
|
||||
/// A result type for starting SAS verifications.
|
||||
pub struct StartSasResult {
|
||||
/// The SAS verification object that got created.
|
||||
pub sas: Sas,
|
||||
pub sas: Arc<Sas>,
|
||||
/// The request that needs to be sent out to notify the other side that a
|
||||
/// SAS verification should start.
|
||||
pub request: OutgoingVerificationRequest,
|
||||
@@ -131,35 +479,16 @@ pub struct StartSasResult {
|
||||
/// A result type for scanning QR codes.
|
||||
pub struct ScanResult {
|
||||
/// The QR code verification object that got created.
|
||||
pub qr: QrCode,
|
||||
pub qr: Arc<QrCode>,
|
||||
/// The request that needs to be sent out to notify the other side that a
|
||||
/// QR code verification should start.
|
||||
pub request: OutgoingVerificationRequest,
|
||||
}
|
||||
|
||||
impl From<InnerSas> for Sas {
|
||||
fn from(sas: InnerSas) -> Self {
|
||||
Self {
|
||||
other_user_id: sas.other_user_id().to_string(),
|
||||
other_device_id: sas.other_device_id().to_string(),
|
||||
flow_id: sas.flow_id().as_str().to_owned(),
|
||||
is_cancelled: sas.is_cancelled(),
|
||||
is_done: sas.is_done(),
|
||||
can_be_presented: sas.can_be_presented(),
|
||||
supports_emoji: sas.supports_emoji(),
|
||||
have_we_confirmed: sas.have_we_confirmed(),
|
||||
we_started: sas.we_started(),
|
||||
room_id: sas.room_id().map(|r| r.to_string()),
|
||||
has_been_accepted: sas.has_been_accepted(),
|
||||
cancel_info: sas.cancel_info().map(|c| c.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A result type for requesting verifications.
|
||||
pub struct RequestVerificationResult {
|
||||
/// The verification request object that got created.
|
||||
pub verification: VerificationRequest,
|
||||
pub verification: Arc<VerificationRequest>,
|
||||
/// The request that needs to be sent out to notify the other side that
|
||||
/// we're requesting verification to begin.
|
||||
pub request: OutgoingVerificationRequest,
|
||||
@@ -175,58 +504,260 @@ pub struct ConfirmVerificationResult {
|
||||
pub signature_request: Option<SignatureUploadRequest>,
|
||||
}
|
||||
|
||||
/// The verificatoin request object which then can transition into some concrete
|
||||
/// verification method
|
||||
pub struct VerificationRequest {
|
||||
/// The other user that is participating in the verification flow
|
||||
pub other_user_id: String,
|
||||
/// The other user's device that is participating in the verification flow
|
||||
pub other_device_id: Option<String>,
|
||||
/// The unique ID of this verification flow, will be a random string for
|
||||
/// to-device events or a event ID for in-room events.
|
||||
pub flow_id: String,
|
||||
/// The room ID where this verification is happening, will be `None` if the
|
||||
/// verification is going through to-device messages
|
||||
pub room_id: Option<String>,
|
||||
/// Did we initiate the verification flow
|
||||
pub we_started: bool,
|
||||
/// Did both parties aggree to verification
|
||||
pub is_ready: bool,
|
||||
/// Did another device respond to the verification request
|
||||
pub is_passive: bool,
|
||||
/// Has the verification completed successfully
|
||||
pub is_done: bool,
|
||||
/// Has the flow been cancelled
|
||||
pub is_cancelled: bool,
|
||||
/// The list of verification methods that the other side advertised as
|
||||
/// supported
|
||||
pub their_methods: Option<Vec<String>>,
|
||||
/// The list of verification methods that we advertised as supported
|
||||
pub our_methods: Option<Vec<String>>,
|
||||
/// Information about the cancellation of the flow, will be `None` if the
|
||||
/// flow hasn't been cancelled
|
||||
pub cancel_info: Option<CancelInfo>,
|
||||
/// Listener that will be passed over the FFI to report changes to a
|
||||
/// verification request.
|
||||
pub trait VerificationRequestListener: Send {
|
||||
/// The callback that should be called on the Rust side
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - The current state of the verification request.
|
||||
fn on_change(&self, state: VerificationRequestState);
|
||||
}
|
||||
|
||||
impl From<InnerVerificationRequest> for VerificationRequest {
|
||||
fn from(v: InnerVerificationRequest) -> Self {
|
||||
Self {
|
||||
other_user_id: v.other_user().to_string(),
|
||||
other_device_id: v.other_device_id().map(|d| d.to_string()),
|
||||
flow_id: v.flow_id().as_str().to_owned(),
|
||||
is_cancelled: v.is_cancelled(),
|
||||
is_done: v.is_done(),
|
||||
is_ready: v.is_ready(),
|
||||
room_id: v.room_id().map(|r| r.to_string()),
|
||||
we_started: v.we_started(),
|
||||
is_passive: v.is_passive(),
|
||||
cancel_info: v.cancel_info().map(|c| c.into()),
|
||||
their_methods: v
|
||||
.their_supported_methods()
|
||||
.map(|v| v.into_iter().map(|m| m.to_string()).collect()),
|
||||
our_methods: v
|
||||
.our_supported_methods()
|
||||
.map(|v| v.into_iter().map(|m| m.to_string()).collect()),
|
||||
/// An Enum describing the state the QrCode verification is in.
|
||||
pub enum VerificationRequestState {
|
||||
/// The verification request was sent
|
||||
Requested,
|
||||
/// The verification request is ready to start a verification flow.
|
||||
Ready {
|
||||
/// The verification methods supported by the other side.
|
||||
their_methods: Vec<String>,
|
||||
|
||||
/// The verification methods supported by the us.
|
||||
our_methods: Vec<String>,
|
||||
},
|
||||
/// The verification flow that was started with this request has finished.
|
||||
Done,
|
||||
/// The verification process has been cancelled.
|
||||
Cancelled {
|
||||
/// Information about the reason of the cancellation.
|
||||
cancel_info: CancelInfo,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<RustVerificationRequestState> for VerificationRequestState {
|
||||
fn from(value: RustVerificationRequestState) -> Self {
|
||||
match value {
|
||||
// The clients do not need to distinguish `Created` and `Requested` state
|
||||
RustVerificationRequestState::Created { .. } => Self::Requested,
|
||||
RustVerificationRequestState::Requested { .. } => Self::Requested,
|
||||
RustVerificationRequestState::Ready {
|
||||
their_methods,
|
||||
our_methods,
|
||||
other_device_id: _,
|
||||
} => Self::Ready {
|
||||
their_methods: their_methods.iter().map(|m| m.to_string()).collect(),
|
||||
our_methods: our_methods.iter().map(|m| m.to_string()).collect(),
|
||||
},
|
||||
RustVerificationRequestState::Done => Self::Done,
|
||||
RustVerificationRequestState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The verificatoin request object which then can transition into some concrete
|
||||
/// verification method
|
||||
pub struct VerificationRequest {
|
||||
pub(crate) inner: InnerVerificationRequest,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
impl VerificationRequest {
|
||||
/// The id of the other user that is participating in this verification
|
||||
/// request.
|
||||
pub fn other_user_id(&self) -> String {
|
||||
self.inner.other_user().to_string()
|
||||
}
|
||||
|
||||
/// The id of the other device that is participating in this verification.
|
||||
pub fn other_device_id(&self) -> Option<String> {
|
||||
self.inner.other_device_id().map(|d| d.to_string())
|
||||
}
|
||||
|
||||
/// Get the unique ID of this verification request
|
||||
pub fn flow_id(&self) -> String {
|
||||
self.inner.flow_id().as_str().to_owned()
|
||||
}
|
||||
|
||||
/// Get the room id if the verification is happening inside a room.
|
||||
pub fn room_id(&self) -> Option<String> {
|
||||
self.inner.room_id().map(|r| r.to_string())
|
||||
}
|
||||
|
||||
/// Has the verification flow that was started with this request finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Is the verification request ready to start a verification flow.
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification request
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Has the verification request been answered by another device.
|
||||
pub fn is_passive(&self) -> bool {
|
||||
self.inner.is_passive()
|
||||
}
|
||||
|
||||
/// Has the verification flow that been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.inner.is_cancelled()
|
||||
}
|
||||
|
||||
/// Get info about the cancellation if the verification request has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
self.inner.cancel_info().map(|v| v.into())
|
||||
}
|
||||
|
||||
/// Get the supported verification methods of the other side.
|
||||
///
|
||||
/// Will be present only if the other side requested the verification or if
|
||||
/// we're in the ready state.
|
||||
pub fn their_supported_methods(&self) -> Option<Vec<String>> {
|
||||
self.inner.their_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect())
|
||||
}
|
||||
|
||||
/// Get our own supported verification methods that we advertised.
|
||||
///
|
||||
/// Will be present only we requested the verification or if we're in the
|
||||
/// ready state.
|
||||
pub fn our_supported_methods(&self) -> Option<Vec<String>> {
|
||||
self.inner.our_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect())
|
||||
}
|
||||
|
||||
/// Accept a verification requests that we share with the given user with
|
||||
/// the given flow id.
|
||||
///
|
||||
/// This will move the verification request into the ready state.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to accept the
|
||||
/// verification requests.
|
||||
///
|
||||
/// * `flow_id` - The ID that uniquely identifies the verification flow.
|
||||
///
|
||||
/// * `methods` - A list of verification methods that we want to advertise
|
||||
/// as supported.
|
||||
pub fn accept(&self, methods: Vec<String>) -> Option<OutgoingVerificationRequest> {
|
||||
let methods = methods.into_iter().map(VerificationMethod::from).collect();
|
||||
self.inner.accept_with_methods(methods).map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Cancel a verification for the given user with the given flow id using
|
||||
/// the given cancel code.
|
||||
pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.cancel().map(|r| r.into())
|
||||
}
|
||||
|
||||
/// Transition from a verification request into short auth string based
|
||||
/// verification.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to start the
|
||||
/// SAS verification.
|
||||
///
|
||||
/// * `flow_id` - The ID of the verification request that initiated the
|
||||
/// verification flow.
|
||||
pub fn start_sas_verification(&self) -> Result<Option<StartSasResult>, CryptoStoreError> {
|
||||
Ok(self.runtime.block_on(self.inner.start_sas())?.map(|(sas, r)| StartSasResult {
|
||||
sas: Arc::new(Sas { inner: sas, runtime: self.runtime.clone() }),
|
||||
request: r.into(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Transition from a verification request into QR code verification.
|
||||
///
|
||||
/// This method should be called when one wants to display a QR code so the
|
||||
/// other side can scan it and move the QR code verification forward.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to start the
|
||||
/// QR code verification.
|
||||
///
|
||||
/// * `flow_id` - The ID of the verification request that initiated the
|
||||
/// verification flow.
|
||||
pub fn start_qr_verification(&self) -> Result<Option<Arc<QrCode>>, CryptoStoreError> {
|
||||
Ok(self
|
||||
.runtime
|
||||
.block_on(self.inner.generate_qr_code())?
|
||||
.map(|qr| QrCode { inner: qr, runtime: self.runtime.clone() }.into()))
|
||||
}
|
||||
|
||||
/// Pass data from a scanned QR code to an active verification request and
|
||||
/// transition into QR code verification.
|
||||
///
|
||||
/// This requires an active `VerificationRequest` to succeed, returns `None`
|
||||
/// if no `VerificationRequest` is found or if the QR code data is invalid.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The ID of the user for which we would like to start the
|
||||
/// QR code verification.
|
||||
///
|
||||
/// * `flow_id` - The ID of the verification request that initiated the
|
||||
/// verification flow.
|
||||
///
|
||||
/// * `data` - The data that was extracted from the scanned QR code as an
|
||||
/// base64 encoded string, without padding.
|
||||
pub fn scan_qr_code(&self, data: &str) -> Option<ScanResult> {
|
||||
let data = STANDARD_NO_PAD.decode(data).ok()?;
|
||||
let data = QrVerificationData::from_bytes(data).ok()?;
|
||||
|
||||
if let Some(qr) = self.runtime.block_on(self.inner.scan_qr_code(data)).ok()? {
|
||||
let request = qr.reciprocate()?;
|
||||
|
||||
Some(ScanResult {
|
||||
qr: QrCode { inner: qr, runtime: self.runtime.clone() }.into(),
|
||||
request: request.into(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a listener for changes in the verification request
|
||||
///
|
||||
/// The given callback will be called whenever the state changes.
|
||||
pub fn set_changes_listener(&self, listener: Box<dyn VerificationRequestListener>) {
|
||||
let stream = self.inner.changes();
|
||||
|
||||
self.runtime.spawn(Self::changes_listener(stream, listener));
|
||||
}
|
||||
|
||||
/// Get the current state of the verification request.
|
||||
pub fn state(&self) -> VerificationRequestState {
|
||||
self.inner.state().into()
|
||||
}
|
||||
|
||||
async fn changes_listener(
|
||||
mut stream: impl Stream<Item = RustVerificationRequestState> + std::marker::Unpin,
|
||||
listener: Box<dyn VerificationRequestListener>,
|
||||
) {
|
||||
while let Some(state) = stream.next().await {
|
||||
// If we receive a done or a cancelled state we're at the end of our road, we
|
||||
// break out of the loop to deallocate the stream and finish the
|
||||
// task.
|
||||
let should_break = matches!(
|
||||
state,
|
||||
RustVerificationRequestState::Done { .. }
|
||||
| RustVerificationRequestState::Cancelled { .. }
|
||||
);
|
||||
|
||||
listener.on_change(state.into());
|
||||
|
||||
if should_break {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
[bindings.kotlin]
|
||||
package_name = "org.matrix.rustcomponents.sdk.crypto"
|
||||
cdylib_name = "matrix_sdk_crypto_ffi"
|
||||
|
||||
[bindings.swift]
|
||||
module_name = "MatrixSDKCrypto"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/pkg
|
||||
@@ -0,0 +1,9 @@
|
||||
// prettier configuration: the same as the conventions used throughout Matrix.org
|
||||
// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js
|
||||
|
||||
module.exports = {
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
quoteProps: "consistent",
|
||||
trailingComma: "all",
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
version-tag-prefix "matrix-sdk-crypto-js-"
|
||||
version-git-message "matrix-sdk-crypto-js v%s"
|
||||
@@ -0,0 +1,5 @@
|
||||
# v0.1.0-alpha.6
|
||||
|
||||
- Add new accessor `InboundGroupSession.senderKey`.
|
||||
- Add a new API, `OlmMachine.registerRoomKeyUpdatedCallback`, which
|
||||
applications can use to listen for received room keys.
|
||||
@@ -8,13 +8,21 @@ keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
rust-version = { workspace = true }
|
||||
version = "0.1.0-alpha.0"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.profiling]
|
||||
wasm-opt = false
|
||||
|
||||
[package.metadata.wasm-pack.profile.profiling.wasm-bindgen]
|
||||
debug-js-glue = false
|
||||
demangle-name-section = true
|
||||
dwarf-debug-info = true
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ['-Oz']
|
||||
|
||||
@@ -27,19 +35,20 @@ qrcode = ["matrix-sdk-crypto/qrcode", "dep:matrix-sdk-qrcode"]
|
||||
tracing = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures-util = "0.3.27"
|
||||
http = { workspace = true }
|
||||
js-sys = "0.3.49"
|
||||
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
|
||||
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
|
||||
matrix-sdk-indexeddb = { version = "0.2.0", path = "../../crates/matrix-sdk-indexeddb", features = ["experimental-nodejs"] }
|
||||
matrix-sdk-indexeddb = { version = "0.2.0", path = "../../crates/matrix-sdk-indexeddb" }
|
||||
matrix-sdk-qrcode = { version = "0.4.0", path = "../../crates/matrix-sdk-qrcode", optional = true }
|
||||
ruma = { version = "0.7.0", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { version = "0.3.0", features = ["js"] }
|
||||
wasm-bindgen = "0.2.80"
|
||||
wasm-bindgen-futures = "0.4.30"
|
||||
js-sys = "0.3.49"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
anyhow = "1.0.58"
|
||||
tracing = { version = "0.1.35", default-features = false, features = ["attributes"] }
|
||||
ruma = { workspace = true, features = ["js", "rand", "unstable-msc2677"] }
|
||||
serde_json = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std"] }
|
||||
zeroize = "1.3.0"
|
||||
vodozemac = { workspace = true, features = ["js"] }
|
||||
wasm-bindgen = "0.2.83"
|
||||
wasm-bindgen-futures = "0.4.33"
|
||||
zeroize = { workspace = true }
|
||||
|
||||
@@ -49,8 +49,6 @@ $ npm run doc
|
||||
|
||||
The documentation is generated in the `./docs` directory.
|
||||
|
||||
|
||||
|
||||
[WebAssembly]: https://webassembly.org/
|
||||
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
|
||||
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Steps for releasing `matrix-sdk-crypto-js`
|
||||
|
||||
1. Create a new branch, named `release-matrix-sdk-crypto-js-<version>`.
|
||||
2. Update `CHANGELOG.md`, if necessary.
|
||||
3. Run `yarn version` to bump the version number and create a tag.
|
||||
4. Push the branch, but not yet the tag.
|
||||
5. Create a PR to approve the changes.
|
||||
6. Once approved:
|
||||
1. Update the git tag to the new head of the branch, if necessary.
|
||||
2. Push the git tag; doing so triggers the github actions workflow which
|
||||
builds and publishes to npm, and creates a draft GH release.
|
||||
3. Merge the PR.
|
||||
7. Update the release on github and publish.
|
||||
@@ -1,61 +0,0 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Matrix SDK Crypto JavaScript Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | filter(attribute="scope", value="crypto-js") | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^refactor", group = "Refactoring"},
|
||||
{ message = "^ci", group = "Continuous Integration"},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = ""
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags chronologically
|
||||
date_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto-js",
|
||||
"version": "0.1.0-alpha.0",
|
||||
"version": "0.1.0-alpha.6",
|
||||
"homepage": "https://github.com/matrix-org/matrix-rust-sdk",
|
||||
"description": "Matrix encryption library, for JavaScript",
|
||||
"license": "Apache-2.0",
|
||||
@@ -18,17 +18,19 @@
|
||||
"ruma",
|
||||
"nio"
|
||||
],
|
||||
"main": "matrix_sdk_crypto.js",
|
||||
"types": "pkg/matrix_sdk_crypto.d.ts",
|
||||
"main": "pkg/matrix_sdk_crypto_js.js",
|
||||
"types": "pkg/matrix_sdk_crypto_js.d.ts",
|
||||
"files": [
|
||||
"pkg/matrix_sdk_crypto_bg.wasm",
|
||||
"pkg/matrix_sdk_crypto.js",
|
||||
"pkg/matrix_sdk_crypto.d.ts"
|
||||
"pkg/matrix_sdk_crypto_js_bg.wasm.js",
|
||||
"pkg/matrix_sdk_crypto_js_bg.wasm.d.ts",
|
||||
"pkg/matrix_sdk_crypto_js.js",
|
||||
"pkg/matrix_sdk_crypto_js.d.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"fake-indexeddb": "^4.0",
|
||||
"jest": "^28.1.0",
|
||||
"prettier": "^2.8.3",
|
||||
"typedoc": "^0.22.17",
|
||||
"wasm-pack": "^0.10.2",
|
||||
"yargs-parser": "~21.0.1"
|
||||
@@ -37,12 +39,11 @@
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --scope matrix-org --out-dir ./pkg",
|
||||
"lint": "prettier --check .",
|
||||
"build": "WASM_PACK_ARGS=--release ./scripts/build.sh",
|
||||
"build:dev": "WASM_PACK_ARGS=--dev ./scripts/build.sh",
|
||||
"test": "jest --verbose",
|
||||
"doc": "typedoc --tsconfig .",
|
||||
"prepack": "npm run build && npm run test",
|
||||
"pack": "wasm-pack pack",
|
||||
"prepublish": "npm run pack",
|
||||
"publish": "wasm-pack publish"
|
||||
"prepack": "npm run build && npm run test"
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Build the JavaScript modules
|
||||
#
|
||||
# This script is really a workaround for https://github.com/rustwasm/wasm-pack/issues/1074.
|
||||
#
|
||||
# Currently, the only reliable way to load WebAssembly in all the JS
|
||||
# environments we want to target (web-via-webpack, web-via-browserify, jest)
|
||||
# seems to be to pack the WASM into base64, and then unpack it and instantiate
|
||||
# it at runtime.
|
||||
#
|
||||
# Hopefully one day, https://github.com/rustwasm/wasm-pack/issues/1074 will be
|
||||
# fixed and this will be unnecessary.
|
||||
|
||||
set -e
|
||||
|
||||
cd $(dirname "$0")/..
|
||||
|
||||
RUSTFLAGS='-C opt-level=z' WASM_BINDGEN_WEAKREF=1 wasm-pack build --target nodejs --scope matrix-org --out-dir pkg "${WASM_PACK_ARGS[@]}"
|
||||
|
||||
# Convert the Wasm into a JS file that exports the base64'ed Wasm.
|
||||
echo "module.exports = \`$(base64 pkg/matrix_sdk_crypto_js_bg.wasm)\`;" > pkg/matrix_sdk_crypto_js_bg.wasm.js
|
||||
|
||||
# In the JavaScript:
|
||||
# 1. Strip out the lines that load the WASM, and our new epilogue.
|
||||
# 2. Remove the imports of `TextDecoder` and `TextEncoder`. We rely on the global defaults.
|
||||
{
|
||||
sed -e '/Text..coder.*= require(.util.)/d' \
|
||||
-e '/^const path = /,$d' pkg/matrix_sdk_crypto_js.js
|
||||
cat scripts/epilogue.js
|
||||
} > pkg/matrix_sdk_crypto_js.js.new
|
||||
mv pkg/matrix_sdk_crypto_js.js.new pkg/matrix_sdk_crypto_js.js
|
||||
|
||||
# also extend the typescript
|
||||
cat scripts/epilogue.d.ts >> pkg/matrix_sdk_crypto_js.d.ts
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Load the WebAssembly module in the background, if it has not already been loaded.
|
||||
*
|
||||
* Returns a promise which will resolve once the other methods are ready.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function initAsync(): Promise<void>;
|
||||
@@ -0,0 +1,88 @@
|
||||
// Initially, 'wasm' is set to a Proxy object which will synchronously load the WebAssembly module and
|
||||
// replace 'wasm' with a reference to the exports from the wasm module.
|
||||
//
|
||||
// Ideally this will never get used because the application will call initAsync instead.
|
||||
wasm = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop, receiver) => __initSync()[prop],
|
||||
},
|
||||
);
|
||||
|
||||
let inited = false;
|
||||
__initSync = function () {
|
||||
if (inited) {
|
||||
return;
|
||||
}
|
||||
if (initPromise) {
|
||||
throw new Error("Asynchronous initialisation already in progress: cannot initialise synchronously");
|
||||
}
|
||||
const bytes = unbase64(require("./matrix_sdk_crypto_js_bg.wasm.js"));
|
||||
const mod = new WebAssembly.Module(bytes);
|
||||
const instance = new WebAssembly.Instance(mod, imports);
|
||||
wasm = instance.exports;
|
||||
wasm.__wbindgen_start();
|
||||
inited = true;
|
||||
return wasm;
|
||||
};
|
||||
|
||||
let initPromise = null;
|
||||
|
||||
/**
|
||||
* Load the WebAssembly module in the background, if it has not already been loaded.
|
||||
*
|
||||
* Returns a promise which will resolve once the other methods are ready.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
module.exports.initAsync = function () {
|
||||
if (inited) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!initPromise) {
|
||||
initPromise = Promise.resolve()
|
||||
.then(() => require("./matrix_sdk_crypto_js_bg.wasm.js"))
|
||||
.then((b64) => WebAssembly.instantiate(unbase64(b64), imports))
|
||||
.then((result) => {
|
||||
wasm = result.instance.exports;
|
||||
wasm.__wbindgen_start();
|
||||
inited = true;
|
||||
});
|
||||
}
|
||||
return initPromise;
|
||||
};
|
||||
|
||||
const b64lookup = new Uint8Array([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 62, 0, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32,
|
||||
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
|
||||
]);
|
||||
|
||||
// base64 decoder, based on the code at https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_%E2%80%93_rewriting_atob_and_btoa_using_typedarrays_and_utf-8
|
||||
function unbase64(sBase64) {
|
||||
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
|
||||
const nInLen = sB64Enc.length;
|
||||
const nOutLen = (nInLen * 3 + 1) >> 2;
|
||||
const taBytes = new Uint8Array(nOutLen);
|
||||
|
||||
let nMod3;
|
||||
let nMod4;
|
||||
let nUint24 = 0;
|
||||
let nOutIdx = 0;
|
||||
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |= b64lookup[sB64Enc.charCodeAt(nInIdx)] << (6 * (3 - nMod4));
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||
nMod3 = 0;
|
||||
while (nMod3 < 3 && nOutIdx < nOutLen) {
|
||||
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
|
||||
nMod3++;
|
||||
nOutIdx++;
|
||||
}
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return taBytes;
|
||||
}
|
||||
@@ -41,13 +41,10 @@ impl Attachment {
|
||||
/// destroyed. It is still possible to get a JSON-encoded backup
|
||||
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
|
||||
pub fn decrypt(attachment: &mut EncryptedAttachment) -> Result<Vec<u8>, JsError> {
|
||||
let media_encryption_info = match attachment.media_encryption_info.take() {
|
||||
Some(media_encryption_info) => media_encryption_info,
|
||||
None => {
|
||||
return Err(JsError::new(
|
||||
"The media encryption info are absent from the given encrypted attachment",
|
||||
))
|
||||
}
|
||||
let Some(media_encryption_info) = attachment.media_encryption_info.take() else {
|
||||
return Err(JsError::new(
|
||||
"The media encryption info are absent from the given encrypted attachment",
|
||||
));
|
||||
};
|
||||
|
||||
let encrypted_data: &[u8] = attachment.encrypted_data.as_slice();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::events;
|
||||
@@ -108,28 +109,48 @@ impl From<matrix_sdk_crypto::types::EventEncryptionAlgorithm> for EncryptionAlgo
|
||||
}
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent an event to us.
|
||||
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
|
||||
/// for more info.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum VerificationState {
|
||||
/// The device is trusted.
|
||||
Trusted,
|
||||
|
||||
/// The device is not trusted.
|
||||
Untrusted,
|
||||
|
||||
/// The device is not known to us.
|
||||
UnknownDevice,
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ShieldColor {
|
||||
/// Important warning
|
||||
Red,
|
||||
/// Low warning
|
||||
Grey,
|
||||
/// No warning
|
||||
None,
|
||||
}
|
||||
|
||||
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
|
||||
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
|
||||
use matrix_sdk_common::deserialized_responses::VerificationState::*;
|
||||
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
|
||||
/// for more info.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShieldState {
|
||||
/// The shield color
|
||||
pub color: ShieldColor,
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ShieldState {
|
||||
/// Error message that can be displayed as a tooltip
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn message(&self) -> Option<String> {
|
||||
self.message.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RustShieldState> for ShieldState {
|
||||
fn from(value: RustShieldState) -> Self {
|
||||
match value {
|
||||
Trusted => Self::Trusted,
|
||||
Untrusted => Self::Untrusted,
|
||||
UnknownDevice => Self::UnknownDevice,
|
||||
RustShieldState::Red { message } => {
|
||||
Self { color: ShieldColor::Red, message: Some(message.to_owned()) }
|
||||
}
|
||||
RustShieldState::Grey { message } => {
|
||||
Self { color: ShieldColor::Grey, message: Some(message.to_owned()) }
|
||||
}
|
||||
RustShieldState::None => Self { color: ShieldColor::None, message: None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::future::Future;
|
||||
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::{JsValue, UnwrapThrowExt};
|
||||
use wasm_bindgen::{JsError, JsValue, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
pub(crate) fn future_to_promise<F, T>(future: F) -> Promise
|
||||
@@ -17,9 +17,9 @@ where
|
||||
spawn_local(async move {
|
||||
match future.await {
|
||||
Ok(value) => resolve.call1(&JsValue::UNDEFINED, &value.into()).unwrap_throw(),
|
||||
Err(value) => {
|
||||
reject.call1(&JsValue::UNDEFINED, &value.to_string().into()).unwrap_throw()
|
||||
}
|
||||
Err(value) => reject
|
||||
.call1(&JsValue::UNDEFINED, &JsError::new(&value.to_string()).into())
|
||||
.unwrap_throw(),
|
||||
};
|
||||
});
|
||||
})
|
||||
|
||||
@@ -130,7 +130,7 @@ impl UserIdentity {
|
||||
}
|
||||
|
||||
/// Create a `VerificationRequest` object after the verification
|
||||
/// request content has been sent out. }
|
||||
/// request content has been sent out.
|
||||
#[wasm_bindgen(js_name = "requestVerification")]
|
||||
pub fn request_verification(
|
||||
&self,
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
#![allow(clippy::drop_non_drop)] // triggered by wasm_bindgen code
|
||||
// triggered by wasm_bindgen code
|
||||
#![allow(clippy::drop_non_drop)]
|
||||
|
||||
pub mod attachment;
|
||||
pub mod device;
|
||||
@@ -37,8 +38,30 @@ pub mod types;
|
||||
pub mod verification;
|
||||
pub mod vodozemac;
|
||||
|
||||
use js_sys::JsString;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Object containing the versions of the Rust libraries we are using.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Versions {
|
||||
/// The version of the vodozemac crate.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub vodozemac: JsString,
|
||||
/// The version of the matrix-sdk-crypto crate.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub matrix_sdk_crypto: JsString,
|
||||
}
|
||||
|
||||
/// Get the versions of the Rust libraries we are using.
|
||||
#[wasm_bindgen(js_name = "getVersions")]
|
||||
pub fn get_versions() -> Versions {
|
||||
Versions {
|
||||
vodozemac: matrix_sdk_crypto::vodozemac::VERSION.into(),
|
||||
matrix_sdk_crypto: matrix_sdk_crypto::VERSION.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run some stuff when the Wasm module is instantiated.
|
||||
///
|
||||
/// Right now, it does the following:
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
//! The crypto specific Olm objects.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::{collections::BTreeMap, ops::Deref};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use js_sys::{Array, Function, Map, Promise, Set};
|
||||
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use tracing::warn;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::{spawn_local, JsFuture};
|
||||
|
||||
use crate::{
|
||||
device, encryption,
|
||||
@@ -13,9 +16,11 @@ use crate::{
|
||||
identifiers, identities,
|
||||
js::downcast,
|
||||
olm, requests,
|
||||
requests::OutgoingRequest,
|
||||
requests::{OutgoingRequest, ToDeviceRequest},
|
||||
responses::{self, response_from_string},
|
||||
store, sync_events, types, verification, vodozemac,
|
||||
store,
|
||||
store::RoomKeyInfo,
|
||||
sync_events, types, verification, vodozemac,
|
||||
};
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
@@ -28,25 +33,38 @@ pub struct OlmMachine {
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl OlmMachine {
|
||||
/// Create a new memory based `OlmMachine`.
|
||||
/// Constructor will always fail. To create a new `OlmMachine`, please use
|
||||
/// the `initialize` method.
|
||||
///
|
||||
/// The created machine will keep the encryption keys only in
|
||||
/// memory and once the objects is dropped, the keys will be lost.
|
||||
/// Why this pattern? `initialize` returns a `Promise`. Returning a
|
||||
// `Promise` from a constructor is not idiomatic in JavaScript.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Result<OlmMachine, JsError> {
|
||||
Err(JsError::new("To build an `OlmMachine`, please use the `initialize` method"))
|
||||
}
|
||||
|
||||
/// Create a new `OlmMachine`.
|
||||
///
|
||||
/// `user_id` represents the unique ID of the user that owns this
|
||||
/// machine. `device_id` represents the unique ID of the device
|
||||
/// The created machine will keep the encryption keys either in a IndexedDB
|
||||
/// based store, or in a memory store and once the objects is dropped,
|
||||
/// the keys will be lost.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - represents the unique ID of the user that owns this
|
||||
/// machine.
|
||||
///
|
||||
/// * `device_id` - represents the unique ID of the device
|
||||
/// that owns this machine.
|
||||
///
|
||||
/// `store_name` and `store_passphrase` are both optional, but
|
||||
/// must be both set to have an effect. If they are both set, the
|
||||
/// state of the machine will persist in a database named
|
||||
/// `store_name` where its content is encrypted by the passphrase
|
||||
/// given by `store_passphrase`. If they are not both set, the
|
||||
/// created machine will keep the encryption keys only in memory,
|
||||
/// and once the object is dropped, the keys will be lost.
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
/// * `store_name` - The name that should be used to open the IndexedDB
|
||||
/// based database. If this isn't provided, a memory-only store will be
|
||||
/// used. *Note* the memory-only store will lose your E2EE keys when the
|
||||
/// `OlmMachine` gets dropped.
|
||||
///
|
||||
/// * `store_passphrase` - The passphrase that should be used to encrypt the
|
||||
/// IndexedDB based
|
||||
pub fn initialize(
|
||||
user_id: &identifiers::UserId,
|
||||
device_id: &identifiers::DeviceId,
|
||||
store_name: Option<String>,
|
||||
@@ -57,15 +75,7 @@ impl OlmMachine {
|
||||
|
||||
future_to_promise(async move {
|
||||
let store = match (store_name, store_passphrase) {
|
||||
// We need this `#[cfg]` because `IndexeddbCryptoStore`
|
||||
// implements `CryptoStore` only on `target_arch =
|
||||
// "wasm32"`. Without that, we could have a compilation
|
||||
// error when checking the entire workspace. In
|
||||
// practise, it doesn't impact this crate because it's
|
||||
// always compiled for `wasm32`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
(Some(store_name), Some(mut store_passphrase)) => {
|
||||
use std::sync::Arc;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let store = Some(
|
||||
@@ -73,8 +83,7 @@ impl OlmMachine {
|
||||
&store_name,
|
||||
&store_passphrase,
|
||||
)
|
||||
.await
|
||||
.map(Arc::new)?,
|
||||
.await?,
|
||||
);
|
||||
|
||||
store_passphrase.zeroize();
|
||||
@@ -82,15 +91,29 @@ impl OlmMachine {
|
||||
store
|
||||
}
|
||||
|
||||
(Some(_), None) => return Err(anyhow::Error::msg("The `store_name` has been set, and so, it expects a `store_passphrase`, which is not set; please provide one")),
|
||||
(Some(store_name), None) => Some(
|
||||
matrix_sdk_indexeddb::IndexeddbCryptoStore::open_with_name(&store_name).await?,
|
||||
),
|
||||
|
||||
(None, Some(_)) => return Err(anyhow::Error::msg("The `store_passphrase` has been set, but it has an effect only if `store_name` is set, which is not; please provide one")),
|
||||
(None, Some(_)) => {
|
||||
return Err(anyhow::Error::msg(
|
||||
"The `store_passphrase` has been set, but it has an effect only if \
|
||||
`store_name` is set, which is not; please provide one",
|
||||
))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
(None, None) => None,
|
||||
};
|
||||
|
||||
Ok(OlmMachine {
|
||||
inner: match store {
|
||||
// We need this `#[cfg]` because `IndexeddbCryptoStore`
|
||||
// implements `CryptoStore` only on `target_arch =
|
||||
// "wasm32"`. Without that, we could have a compilation
|
||||
// error when checking the entire workspace. In practice,
|
||||
// it doesn't impact this crate because it's always
|
||||
// compiled for `wasm32`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Some(store) => {
|
||||
matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id.as_ref(),
|
||||
@@ -99,7 +122,7 @@ impl OlmMachine {
|
||||
)
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
_ => {
|
||||
matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
|
||||
.await
|
||||
}
|
||||
@@ -134,30 +157,42 @@ impl OlmMachine {
|
||||
future_to_promise(async move { Ok(me.display_name().await?) })
|
||||
}
|
||||
|
||||
/// Get all the tracked users of our own device.
|
||||
/// Get the list of users whose devices we are currently tracking.
|
||||
///
|
||||
/// A user can be marked for tracking using the
|
||||
/// [`update_tracked_users`](#method.update_tracked_users) method.
|
||||
///
|
||||
/// Returns a `Set<UserId>`.
|
||||
#[wasm_bindgen(js_name = "trackedUsers")]
|
||||
pub fn tracked_users(&self) -> Set {
|
||||
pub fn tracked_users(&self) -> Result<Promise, JsError> {
|
||||
let set = Set::new(&JsValue::UNDEFINED);
|
||||
let me = self.inner.clone();
|
||||
|
||||
for user in self.inner.tracked_users() {
|
||||
set.add(&identifiers::UserId::from(user).into());
|
||||
}
|
||||
|
||||
set
|
||||
Ok(future_to_promise(async move {
|
||||
for user in me.tracked_users().await? {
|
||||
set.add(&identifiers::UserId::from(user).into());
|
||||
}
|
||||
Ok(set)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Update the tracked users.
|
||||
/// Update the list of tracked users.
|
||||
///
|
||||
/// `users` is an iterator over user IDs that should be marked for
|
||||
/// tracking.
|
||||
/// The OlmMachine maintains a list of users whose devices we are keeping
|
||||
/// track of: these are known as "tracked users". These must be users
|
||||
/// that we share a room with, so that the server sends us updates for
|
||||
/// their device lists.
|
||||
///
|
||||
/// This will mark users that weren't seen before for a key query
|
||||
/// and tracking.
|
||||
/// # Arguments
|
||||
///
|
||||
/// If the user is already known to the Olm machine, it will not
|
||||
/// be considered for a key query.
|
||||
/// * `users` - An array of user ids that should be added to the list of
|
||||
/// tracked users
|
||||
///
|
||||
/// Any users that hadn't been seen before will be flagged for a key query
|
||||
/// immediately, and whenever `receive_sync_changes` receives a
|
||||
/// "changed" notification for that user in the future.
|
||||
///
|
||||
/// Users that were already in the list are unaffected.
|
||||
#[wasm_bindgen(js_name = "updateTrackedUsers")]
|
||||
pub fn update_tracked_users(&self, users: &Array) -> Result<Promise, JsError> {
|
||||
let users = users
|
||||
@@ -168,7 +203,7 @@ impl OlmMachine {
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
|
||||
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await?;
|
||||
Ok(JsValue::UNDEFINED)
|
||||
}))
|
||||
}
|
||||
@@ -452,6 +487,8 @@ impl OlmMachine {
|
||||
/// `room_id` is the room ID. `users` is an array of `UserId`
|
||||
/// objects. `encryption_settings` are an `EncryptionSettings`
|
||||
/// object.
|
||||
///
|
||||
/// Returns an array of `ToDeviceRequest`s.
|
||||
#[wasm_bindgen(js_name = "shareRoomKey")]
|
||||
pub fn share_room_key(
|
||||
&self,
|
||||
@@ -470,20 +507,27 @@ impl OlmMachine {
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(serde_json::to_string(
|
||||
&me.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
|
||||
.await?,
|
||||
)?)
|
||||
let to_device_requests = me
|
||||
.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
|
||||
.await?;
|
||||
|
||||
// convert each request to our own ToDeviceRequest struct, and then wrap it in a
|
||||
// JsValue.
|
||||
//
|
||||
// Then collect the results into a javascript Array, throwing any errors into
|
||||
// the promise.
|
||||
Ok(to_device_requests
|
||||
.into_iter()
|
||||
.map(|td| ToDeviceRequest::try_from(td.deref()).map(JsValue::from))
|
||||
.collect::<Result<Array, _>>()?)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the a key claiming request for the user/device pairs that
|
||||
/// we are missing Olm sessions for.
|
||||
///
|
||||
/// Returns `NULL` if no key claiming request needs to be sent
|
||||
/// out, otherwise it returns an `Array` where the first key is
|
||||
/// the transaction ID as a string, and the second key is the keys
|
||||
/// claim request serialized to JSON.
|
||||
/// Returns `null` if no key claiming request needs to be sent
|
||||
/// out, otherwise it returns a `KeysClaimRequest` object.
|
||||
///
|
||||
/// Sessions need to be established between devices so group
|
||||
/// sessions for a room can be shared with them.
|
||||
@@ -606,24 +650,24 @@ impl OlmMachine {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Receive an unencrypted verification event.
|
||||
/// Receive a verification event.
|
||||
///
|
||||
/// This method can be used to pass verification events that are
|
||||
/// happening in unencrypted rooms to the `OlmMachine`.
|
||||
///
|
||||
/// Note: This does not need to be called for encrypted events
|
||||
/// since those will get passed to the `OlmMachine` during
|
||||
/// decryption.
|
||||
#[wasm_bindgen(js_name = "receiveUnencryptedVerificationEvent")]
|
||||
pub fn receive_unencrypted_verification_event(&self, event: &str) -> Result<Promise, JsError> {
|
||||
let event: ruma::events::AnyMessageLikeEvent = serde_json::from_str(event)?;
|
||||
/// This method can be used to pass verification events that are happening
|
||||
/// in rooms to the `OlmMachine`. The event should be in the decrypted form.
|
||||
#[wasm_bindgen(js_name = "receiveVerificationEvent")]
|
||||
pub fn receive_verification_event(
|
||||
&self,
|
||||
event: &str,
|
||||
room_id: &identifiers::RoomId,
|
||||
) -> Result<Promise, JsError> {
|
||||
let room_id = room_id.inner.clone();
|
||||
let event: ruma::events::AnySyncMessageLikeEvent = serde_json::from_str(event)?;
|
||||
let event = event.into_full_event(room_id);
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(me
|
||||
.receive_unencrypted_verification_event(&event)
|
||||
.await
|
||||
.map(|_| JsValue::UNDEFINED)?)
|
||||
Ok(me.receive_verification_event(&event).await.map(|_| JsValue::UNDEFINED)?)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -728,4 +772,69 @@ impl OlmMachine {
|
||||
passphrase,
|
||||
)?)?)
|
||||
}
|
||||
|
||||
/// Register a callback which will be called whenever there is an update to
|
||||
/// a room key.
|
||||
///
|
||||
/// `callback` should be a function that takes a single argument (an array
|
||||
/// of {@link RoomKeyInfo}) and returns a Promise.
|
||||
#[wasm_bindgen(js_name = "registerRoomKeyUpdatedCallback")]
|
||||
pub async fn register_room_key_updated_callback(&self, callback: Function) {
|
||||
let stream = self.inner.store().room_keys_received_stream();
|
||||
|
||||
// fire up a promise chain which will call `cb` on each result from the stream
|
||||
spawn_local(async move {
|
||||
// take a reference to `callback` (which we then pass into the closure), to stop
|
||||
// the callback being moved into the closure (which would mean we could only
|
||||
// call the closure once)
|
||||
let callback_ref = &callback;
|
||||
stream.for_each(move |item| send_room_key_info_to_callback(callback_ref, item)).await;
|
||||
});
|
||||
}
|
||||
|
||||
/// Shut down the `OlmMachine`.
|
||||
///
|
||||
/// The `OlmMachine` cannot be used after this method has been called.
|
||||
///
|
||||
/// All associated resources will be closed too, like IndexedDB
|
||||
/// connections.
|
||||
pub fn close(self) {}
|
||||
}
|
||||
|
||||
// helper for register_room_key_received_callback: wraps the key info
|
||||
// into our own RoomKeyInfo struct, and passes it into the javascript
|
||||
// function
|
||||
async fn send_room_key_info_to_callback(
|
||||
callback: &Function,
|
||||
room_key_info: Vec<matrix_sdk_crypto::store::RoomKeyInfo>,
|
||||
) {
|
||||
let rki: Array = room_key_info.into_iter().map(RoomKeyInfo::from).map(JsValue::from).collect();
|
||||
match promise_result_to_future(callback.call1(&JsValue::NULL, &rki)).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
warn!("Error calling room-key-received callback: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a result from a javascript function which returns a Promise (or throws
|
||||
/// an exception before returning one), convert the result to a rust Future
|
||||
/// which completes with the result of the promise
|
||||
async fn promise_result_to_future(res: Result<JsValue, JsValue>) -> Result<JsValue, JsValue> {
|
||||
match res {
|
||||
Ok(retval) => {
|
||||
if !retval.has_type::<Promise>() {
|
||||
panic!("not a promise");
|
||||
}
|
||||
let prom: Promise = retval.dyn_into().map_err(|v| {
|
||||
JsError::new(&format!("function returned a non-Promise value {v:?}"))
|
||||
})?;
|
||||
JsFuture::from(prom).await
|
||||
}
|
||||
Err(e) => {
|
||||
// the function threw an exception before it returned the promise. We can just
|
||||
// return the error as an error result.
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{identifiers, impl_from_to_inner};
|
||||
use crate::{identifiers, impl_from_to_inner, vodozemac::Curve25519PublicKey};
|
||||
|
||||
/// Struct representing the state of our private cross signing keys,
|
||||
/// it shows which private cross signing keys we have locally stored.
|
||||
@@ -57,6 +57,13 @@ impl InboundGroupSession {
|
||||
self.inner.room_id().to_owned().into()
|
||||
}
|
||||
|
||||
/// The Curve25519 key of the sender of this session, as a
|
||||
/// [Curve25519PublicKey].
|
||||
#[wasm_bindgen(getter, js_name = "senderKey")]
|
||||
pub fn sender_key(&self) -> Curve25519PublicKey {
|
||||
self.inner.sender_key().into()
|
||||
}
|
||||
|
||||
/// Returns the unique identifier for this session.
|
||||
#[wasm_bindgen(getter, js_name = "sessionId")]
|
||||
pub fn session_id(&self) -> String {
|
||||
|
||||
@@ -11,10 +11,13 @@ use matrix_sdk_crypto::{
|
||||
},
|
||||
OutgoingRequests,
|
||||
};
|
||||
use ruma::api::client::keys::{
|
||||
claim_keys::v3::Request as OriginalKeysClaimRequest,
|
||||
upload_keys::v3::Request as OriginalKeysUploadRequest,
|
||||
upload_signatures::v3::Request as OriginalSignatureUploadRequest,
|
||||
use ruma::{
|
||||
api::client::keys::{
|
||||
claim_keys::v3::Request as OriginalKeysClaimRequest,
|
||||
upload_keys::v3::Request as OriginalKeysUploadRequest,
|
||||
upload_signatures::v3::Request as OriginalSignatureUploadRequest,
|
||||
},
|
||||
events::EventContent,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@@ -33,11 +36,10 @@ pub struct KeysUploadRequest {
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A JSON-encoded string containing the rest of the payload: `device_keys`,
|
||||
/// `one_time_keys`, `fallback_keys`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"device_keys": …, "one_time_keys": …, "fallback_keys": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
@@ -70,11 +72,10 @@ pub struct KeysQueryRequest {
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A JSON-encoded string containing the rest of the payload: `timeout`,
|
||||
/// `device_keys`, `token`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"timeout": …, "device_keys": …, "token": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
@@ -108,11 +109,10 @@ pub struct KeysClaimRequest {
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A JSON-encoded string containing the rest of the payload: `timeout`,
|
||||
/// `one_time_keys`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"timeout": …, "one_time_keys": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
@@ -145,11 +145,18 @@ pub struct ToDeviceRequest {
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A string representing the type of event being sent to each devices.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub event_type: JsString,
|
||||
|
||||
/// A string representing a request identifier unique to the access token
|
||||
/// used to send the request.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub txn_id: JsString,
|
||||
|
||||
/// A JSON-encoded string containing the rest of the payload: `messages`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"event_type": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
@@ -158,8 +165,13 @@ pub struct ToDeviceRequest {
|
||||
impl ToDeviceRequest {
|
||||
/// Create a new `ToDeviceRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> ToDeviceRequest {
|
||||
Self { id: Some(id), body }
|
||||
pub fn new(
|
||||
id: JsString,
|
||||
event_type: JsString,
|
||||
txn_id: JsString,
|
||||
body: JsString,
|
||||
) -> ToDeviceRequest {
|
||||
Self { id: Some(id), event_type, txn_id, body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -182,11 +194,9 @@ pub struct SignatureUploadRequest {
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A JSON-encoded string containing the rest of the payload: `signed_keys`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"signed_keys": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
@@ -217,21 +227,39 @@ pub struct RoomMessageRequest {
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"room_id": …, "txn_id": …, "content": …}
|
||||
/// ```
|
||||
/// A string representing the room to send the event to.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
pub room_id: JsString,
|
||||
|
||||
/// A string representing the transaction ID for this event.
|
||||
///
|
||||
/// Clients should generate an ID unique across requests with the same
|
||||
/// access token; it will be used by the server to ensure idempotency of
|
||||
/// requests.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub txn_id: JsString,
|
||||
|
||||
/// A string representing the type of event to be sent.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub event_type: JsString,
|
||||
|
||||
/// A JSON-encoded string containing the message's content.
|
||||
#[wasm_bindgen(readonly, js_name = "body")]
|
||||
pub content: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoomMessageRequest {
|
||||
/// Create a new `RoomMessageRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> RoomMessageRequest {
|
||||
Self { id: Some(id), body }
|
||||
pub fn new(
|
||||
id: JsString,
|
||||
room_id: JsString,
|
||||
txn_id: JsString,
|
||||
event_type: JsString,
|
||||
content: JsString,
|
||||
) -> RoomMessageRequest {
|
||||
Self { id: Some(id), room_id, txn_id, event_type, content }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -252,32 +280,9 @@ pub struct KeysBackupRequest {
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A JSON-encoded string containing the rest of the payload: `rooms`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"rooms": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/** Other Requests * */
|
||||
|
||||
/// Request that will publish a cross signing identity.
|
||||
///
|
||||
/// This uploads the public cross signing key triplet.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct SigningKeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"master_key": …, "self_signing_key": …, "user_signing_key": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
@@ -297,28 +302,42 @@ impl KeysBackupRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/** Other Requests * */
|
||||
|
||||
/// Request that will publish a cross signing identity.
|
||||
///
|
||||
/// This uploads the public cross signing key triplet.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct SigningKeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded string containing the rest of the payload: `master_key`,
|
||||
/// `self_signing_key`, `user_signing_key`.
|
||||
///
|
||||
/// It represents the body of the HTTP request.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
macro_rules! request {
|
||||
($destination_request:ident from $source_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
|
||||
impl $destination_request {
|
||||
pub(crate) fn to_json(request: &$source_request) -> Result<String, serde_json::Error> {
|
||||
let mut map = serde_json::Map::new();
|
||||
$(
|
||||
map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap());
|
||||
)+
|
||||
let object = serde_json::Value::Object(map);
|
||||
|
||||
serde_json::to_string(&object)
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
$destination_request:ident from $source_request:ident
|
||||
$( extracts $( $field_name:ident : $field_type:tt ),+ $(,)? )?
|
||||
$( $( and )? groups $( $grouped_field_name:ident ),+ $(,)? )?
|
||||
) => {
|
||||
impl TryFrom<&$source_request> for $destination_request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(request: &$source_request) -> Result<Self, Self::Error> {
|
||||
Ok($destination_request {
|
||||
id: None,
|
||||
body: Self::to_json(request)?.into(),
|
||||
})
|
||||
request!(
|
||||
@__try_from $destination_request from $source_request
|
||||
(request_id = None, request = request)
|
||||
$( extracts [ $( $field_name : $field_type, )+ ] )?
|
||||
$( groups [ $( $grouped_field_name, )+ ] )?
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,26 +347,73 @@ macro_rules! request {
|
||||
fn try_from(
|
||||
(request_id, request): (String, &$source_request),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok($destination_request {
|
||||
id: Some(request_id.into()),
|
||||
body: Self::to_json(request)?.into(),
|
||||
})
|
||||
request!(
|
||||
@__try_from $destination_request from $source_request
|
||||
(request_id = Some(request_id.into()), request = request)
|
||||
$( extracts [ $( $field_name : $field_type, )+ ] )?
|
||||
$( groups [ $( $grouped_field_name, )+ ] )?
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
@__try_from $destination_request:ident from $source_request:ident
|
||||
(request_id = $request_id:expr, request = $request:expr)
|
||||
$( extracts [ $( $field_name:ident : $field_type:tt ),* $(,)? ] )?
|
||||
$( groups [ $( $grouped_field_name:ident ),* $(,)? ] )?
|
||||
) => {
|
||||
{
|
||||
Ok($destination_request {
|
||||
id: $request_id,
|
||||
$(
|
||||
$(
|
||||
$field_name: request!(@__field $field_name : $field_type ; request = $request),
|
||||
)*
|
||||
)?
|
||||
$(
|
||||
body: {
|
||||
let mut map = serde_json::Map::new();
|
||||
$(
|
||||
map.insert(stringify!($grouped_field_name).to_owned(), serde_json::to_value(&$request.$grouped_field_name).unwrap());
|
||||
)*
|
||||
let object = serde_json::Value::Object(map);
|
||||
|
||||
serde_json::to_string(&object)?.into()
|
||||
}
|
||||
)?
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
( @__field $field_name:ident : $field_type:ident ; request = $request:expr ) => {
|
||||
request!(@__field_type as $field_type ; request = $request, field_name = $field_name)
|
||||
};
|
||||
|
||||
( @__field_type as string ; request = $request:expr, field_name = $field_name:ident ) => {
|
||||
$request.$field_name.to_string().into()
|
||||
};
|
||||
|
||||
( @__field_type as json ; request = $request:expr, field_name = $field_name:ident ) => {
|
||||
serde_json::to_string(&$request.$field_name)?.into()
|
||||
};
|
||||
|
||||
( @__field_type as event_type ; request = $request:expr, field_name = $field_name:ident ) => {
|
||||
$request.content.event_type().to_string().into()
|
||||
};
|
||||
}
|
||||
|
||||
// Outgoing Requests
|
||||
request!(KeysUploadRequest from OriginalKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from OriginalKeysQueryRequest maps fields timeout, device_keys, token);
|
||||
request!(KeysClaimRequest from OriginalKeysClaimRequest maps fields timeout, one_time_keys);
|
||||
request!(ToDeviceRequest from OriginalToDeviceRequest maps fields event_type, txn_id, messages);
|
||||
request!(SignatureUploadRequest from OriginalSignatureUploadRequest maps fields signed_keys);
|
||||
request!(RoomMessageRequest from OriginalRoomMessageRequest maps fields room_id, txn_id, content);
|
||||
request!(KeysBackupRequest from OriginalKeysBackupRequest maps fields rooms);
|
||||
request!(KeysUploadRequest from OriginalKeysUploadRequest groups device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from OriginalKeysQueryRequest groups timeout, device_keys, token);
|
||||
request!(KeysClaimRequest from OriginalKeysClaimRequest groups timeout, one_time_keys);
|
||||
request!(ToDeviceRequest from OriginalToDeviceRequest extracts event_type: string, txn_id: string and groups messages);
|
||||
request!(SignatureUploadRequest from OriginalSignatureUploadRequest groups signed_keys);
|
||||
request!(RoomMessageRequest from OriginalRoomMessageRequest extracts room_id: string, txn_id: string, event_type: event_type, content: json);
|
||||
request!(KeysBackupRequest from OriginalKeysBackupRequest groups rooms);
|
||||
|
||||
// Other Requests
|
||||
request!(SigningKeysUploadRequest from OriginalUploadSigningKeysRequest maps fields master_key, self_signing_key, user_signing_key);
|
||||
request!(SigningKeysUploadRequest from OriginalUploadSigningKeysRequest groups master_key, self_signing_key, user_signing_key);
|
||||
|
||||
// JavaScript has no complex enums like Rust. To return structs of
|
||||
// different types, we have no choice that hiding everything behind a
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Types related to responses.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use js_sys::{Array, JsString};
|
||||
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
|
||||
use matrix_sdk_crypto::IncomingResponse;
|
||||
@@ -156,7 +154,7 @@ impl DecryptedRoomEvent {
|
||||
/// trusted.
|
||||
#[wasm_bindgen(getter, js_name = "senderDevice")]
|
||||
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
|
||||
Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone()))
|
||||
Some(self.encryption_info.as_ref()?.sender_device.as_ref()?.clone().into())
|
||||
}
|
||||
|
||||
/// The Curve25519 key of the device that created the megolm
|
||||
@@ -190,9 +188,15 @@ impl DecryptedRoomEvent {
|
||||
/// note this is the state of the device at the time of
|
||||
/// decryption. It may change in the future if a device gets
|
||||
/// verified or deleted.
|
||||
#[wasm_bindgen(getter, js_name = "verificationState")]
|
||||
pub fn verification_state(&self) -> Option<encryption::VerificationState> {
|
||||
Some((self.encryption_info.as_ref()?.verification_state.borrow()).into())
|
||||
#[wasm_bindgen(js_name = "shieldState")]
|
||||
pub fn shield_state(&self, strict: bool) -> Option<encryption::ShieldState> {
|
||||
let state = &self.encryption_info.as_ref()?.verification_state;
|
||||
|
||||
if strict {
|
||||
Some(state.to_shield_state_strict().into())
|
||||
} else {
|
||||
Some(state.to_shield_state_lax().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::impl_from_to_inner;
|
||||
use crate::{
|
||||
encryption::EncryptionAlgorithm, identifiers::RoomId, impl_from_to_inner,
|
||||
vodozemac::Curve25519PublicKey,
|
||||
};
|
||||
|
||||
/// A struct containing private cross signing keys that can be backed
|
||||
/// up or uploaded to the secret store.
|
||||
@@ -34,3 +37,40 @@ impl CrossSigningKeyExport {
|
||||
self.inner.user_signing_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information on a room key that has been received or imported.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct RoomKeyInfo {
|
||||
pub(crate) inner: matrix_sdk_crypto::store::RoomKeyInfo,
|
||||
}
|
||||
|
||||
impl_from_to_inner!(matrix_sdk_crypto::store::RoomKeyInfo => RoomKeyInfo);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoomKeyInfo {
|
||||
/// The {@link EncryptionAlgorithm} that this key is used for. Will be one
|
||||
/// of the `m.megolm.*` algorithms.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn algorithm(&self) -> EncryptionAlgorithm {
|
||||
self.inner.algorithm.clone().into()
|
||||
}
|
||||
|
||||
/// The room where the key is used.
|
||||
#[wasm_bindgen(getter, js_name = "roomId")]
|
||||
pub fn room_id(&self) -> RoomId {
|
||||
self.inner.room_id.clone().into()
|
||||
}
|
||||
|
||||
/// The Curve25519 key of the device which initiated the session originally.
|
||||
#[wasm_bindgen(getter, js_name = "senderKey")]
|
||||
pub fn sender_key(&self) -> Curve25519PublicKey {
|
||||
self.inner.sender_key.into()
|
||||
}
|
||||
|
||||
/// The ID of the session that the key is for.
|
||||
#[wasm_bindgen(getter, js_name = "sessionId")]
|
||||
pub fn session_id(&self) -> String {
|
||||
self.inner.session_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{identifiers, js::downcast};
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceLists {
|
||||
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
|
||||
pub(crate) inner: ruma::api::client::sync::sync_events::DeviceLists,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@@ -19,7 +19,7 @@ impl DeviceLists {
|
||||
/// `changed` and `left` must be an array of `UserId`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(changed: Option<Array>, left: Option<Array>) -> Result<DeviceLists, JsError> {
|
||||
let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default();
|
||||
let mut inner = ruma::api::client::sync::sync_events::DeviceLists::default();
|
||||
|
||||
inner.changed = changed
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -145,9 +145,6 @@ mod inner {
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "trace")]
|
||||
fn log_trace(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "debug")]
|
||||
fn log_debug(message: String);
|
||||
|
||||
@@ -213,7 +210,7 @@ mod inner {
|
||||
let message = format!("{level} {origin}{recorder}");
|
||||
|
||||
match *level {
|
||||
Level::TRACE => log_trace(message),
|
||||
Level::TRACE => log_debug(message),
|
||||
Level::DEBUG => log_debug(message),
|
||||
Level::INFO => log_info(message),
|
||||
Level::WARN => log_warn(message),
|
||||
|
||||
@@ -66,8 +66,7 @@ impl TryFrom<JsValue> for VerificationMethod {
|
||||
3 => Self::ReciprocateV1,
|
||||
_ => {
|
||||
return Err(JsError::new(&format!(
|
||||
"Unknown verification method (received `{:?}`)",
|
||||
value
|
||||
"Unknown verification method (received `{value:?}`)"
|
||||
)))
|
||||
}
|
||||
})
|
||||
@@ -100,8 +99,7 @@ impl TryFrom<RumaVerificationMethod> for VerificationMethod {
|
||||
ReciprocateV1 => Self::ReciprocateV1,
|
||||
_ => {
|
||||
return Err(JsError::new(&format!(
|
||||
"Unknown verification method (received `{:?}`)",
|
||||
value
|
||||
"Unknown verification method (received `{value:?}`)"
|
||||
)))
|
||||
}
|
||||
})
|
||||
@@ -484,8 +482,13 @@ impl Qr {
|
||||
/// The `to_qr_code` method can be used to instead output a QrCode
|
||||
/// object that can be rendered.
|
||||
#[wasm_bindgen(js_name = "toBytes")]
|
||||
pub fn to_bytes(&self) -> Result<Array, JsError> {
|
||||
Ok(self.inner.to_bytes()?.into_iter().map(JsValue::from).collect())
|
||||
pub fn to_bytes(&self) -> Result<Uint8ClampedArray, JsError> {
|
||||
let bytes = self.inner.to_bytes()?;
|
||||
let output = Uint8ClampedArray::new_with_length(bytes.len() as _);
|
||||
|
||||
output.copy_from(&bytes);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Notify the other side that we have successfully scanned the QR
|
||||
@@ -754,10 +757,10 @@ impl QrCodeScan {
|
||||
/// This method is useful if you would like to do your own custom QR code
|
||||
/// decoding.
|
||||
#[wasm_bindgen(js_name = "fromBytes")]
|
||||
pub fn from_bytes(buffer: Uint8ClampedArray) -> Result<QrCodeScan, JsError> {
|
||||
pub fn from_bytes(buffer: &Uint8ClampedArray) -> Result<QrCodeScan, JsError> {
|
||||
let bytes = buffer.to_vec();
|
||||
|
||||
Ok(Self { inner: matrix_sdk_qrcode::QrVerificationData::from_bytes(&bytes)? })
|
||||
Ok(Self { inner: matrix_sdk_qrcode::QrVerificationData::from_bytes(bytes)? })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1029,9 +1032,9 @@ impl VerificationRequest {
|
||||
/// for this verification flow.
|
||||
#[cfg(feature = "qrcode")]
|
||||
#[wasm_bindgen(js_name = "scanQrCode")]
|
||||
pub fn scan_qr_code(&self, data: QrCodeScan) -> Promise {
|
||||
pub fn scan_qr_code(&self, data: &QrCodeScan) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
let qr_verification_data = data.inner;
|
||||
let qr_verification_data = data.inner.clone();
|
||||
|
||||
future_to_promise(
|
||||
async move { Ok(me.scan_qr_code(qr_verification_data).await?.map(Qr::from)) },
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
const { UserId, initAsync } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
test("can instantiate rust objects with async initialiser", async () => {
|
||||
initUserId = () => new UserId("@foo:bar.org");
|
||||
|
||||
// stub out the synchronous WebAssembly loader with one that raises an error
|
||||
jest.spyOn(WebAssembly, "Module").mockImplementation(() => {
|
||||
throw new Error("synchronous WebAssembly.Module() not allowed");
|
||||
});
|
||||
|
||||
// this should fail
|
||||
expect(initUserId).toThrow(/synchronous/);
|
||||
|
||||
// but once we init with async, it should work
|
||||
await initAsync();
|
||||
initUserId();
|
||||
});
|
||||
@@ -1,37 +1,41 @@
|
||||
const { Attachment, EncryptedAttachment } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { Attachment, EncryptedAttachment } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe(Attachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const originalData = "hello";
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
let encryptedAttachment;
|
||||
|
||||
test('can encrypt data', () => {
|
||||
|
||||
test("can encrypt data", () => {
|
||||
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
|
||||
|
||||
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
|
||||
|
||||
expect(mediaEncryptionInfo).toMatchObject({
|
||||
v: 'v2',
|
||||
v: "v2",
|
||||
key: {
|
||||
kty: expect.any(String),
|
||||
key_ops: expect.arrayContaining(['encrypt', 'decrypt']),
|
||||
key_ops: expect.arrayContaining(["encrypt", "decrypt"]),
|
||||
alg: expect.any(String),
|
||||
k: expect.any(String),
|
||||
ext: expect.any(Boolean),
|
||||
},
|
||||
iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
hashes: {
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/)
|
||||
}
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
},
|
||||
});
|
||||
|
||||
const encryptedData = encryptedAttachment.encryptedData;
|
||||
expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false);
|
||||
expect(
|
||||
encryptedData.every((i) => {
|
||||
i != 0;
|
||||
}),
|
||||
).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can decrypt data', () => {
|
||||
test("can decrypt data", () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
|
||||
@@ -40,34 +44,36 @@ describe(Attachment.name, () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can only decrypt once', () => {
|
||||
test("can only decrypt once", () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
|
||||
expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow()
|
||||
expect(() => {
|
||||
textDecoder.decode(decryptedAttachment);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptedAttachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const originalData = "hello";
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
test('can be created manually', () => {
|
||||
test("can be created manually", () => {
|
||||
const encryptedAttachment = new EncryptedAttachment(
|
||||
new Uint8Array([24, 150, 67, 37, 144]),
|
||||
JSON.stringify({
|
||||
v: 'v2',
|
||||
v: "v2",
|
||||
key: {
|
||||
kty: 'oct',
|
||||
key_ops: [ 'encrypt', 'decrypt' ],
|
||||
alg: 'A256CTR',
|
||||
k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM',
|
||||
ext: true
|
||||
kty: "oct",
|
||||
key_ops: ["encrypt", "decrypt"],
|
||||
alg: "A256CTR",
|
||||
k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM",
|
||||
ext: true,
|
||||
},
|
||||
iv: 'xk2AcWkomiYAAAAAAAAAAA',
|
||||
iv: "xk2AcWkomiYAAAAAAAAAAA",
|
||||
hashes: {
|
||||
sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok'
|
||||
}
|
||||
})
|
||||
sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
@@ -27,11 +27,11 @@ const {
|
||||
Qr,
|
||||
QrCode,
|
||||
QrCodeScan,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { zip, addMachineToMachine } = require('./helper');
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
const { zip, addMachineToMachine } = require("./helper");
|
||||
|
||||
describe('LocalTrust', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("LocalTrust", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(LocalTrust.Verified).toStrictEqual(0);
|
||||
expect(LocalTrust.BlackListed).toStrictEqual(1);
|
||||
expect(LocalTrust.Ignored).toStrictEqual(2);
|
||||
@@ -39,8 +39,8 @@ describe('LocalTrust', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeviceKeyName', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("DeviceKeyName", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(DeviceKeyName.Curve25519).toStrictEqual(0);
|
||||
expect(DeviceKeyName.Ed25519).toStrictEqual(1);
|
||||
expect(DeviceKeyName.Unknown).toStrictEqual(2);
|
||||
@@ -48,28 +48,58 @@ describe('DeviceKeyName', () => {
|
||||
});
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("foobar");
|
||||
const room = new RoomId("!baz:matrix.org");
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || user, new_device || device);
|
||||
return OlmMachine.initialize(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can read user devices', async () => {
|
||||
test("can read user devices", async () => {
|
||||
const m = await machine();
|
||||
const userDevices = await m.getUserDevices(user);
|
||||
|
||||
expect(userDevices).toBeInstanceOf(UserDevices);
|
||||
expect(userDevices.get(device)).toBeInstanceOf(Device);
|
||||
expect(userDevices.isAnyVerified()).toStrictEqual(false);
|
||||
expect(userDevices.keys().map(device_id => device_id.toString())).toStrictEqual([device.toString()]);
|
||||
expect(userDevices.devices().map(device => device.deviceId.toString())).toStrictEqual([device.toString()]);
|
||||
expect(userDevices.keys().map((device_id) => device_id.toString())).toStrictEqual([device.toString()]);
|
||||
expect(userDevices.devices().map((device) => device.deviceId.toString())).toStrictEqual([device.toString()]);
|
||||
});
|
||||
|
||||
test('can read a user device', async () => {
|
||||
test("can read a user device", async () => {
|
||||
const m = await machine();
|
||||
const dev = await m.getDevice(user, device);
|
||||
|
||||
const hypothetical_response = JSON.stringify({
|
||||
device_keys: {
|
||||
"@alice:example.org": {
|
||||
JLAFKJWSCS: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "JLAFKJWSCS",
|
||||
keys: {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM",
|
||||
},
|
||||
signatures: {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS":
|
||||
"m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA",
|
||||
},
|
||||
},
|
||||
unsigned: {
|
||||
device_display_name: "Alice's mobile phone",
|
||||
},
|
||||
user_id: "@alice:example.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
failures: {},
|
||||
});
|
||||
// Insert another device into the store
|
||||
await m.markRequestAsSent("ID", RequestType.KeysQuery, hypothetical_response);
|
||||
|
||||
const secondDeviceId = new DeviceId("JLAFKJWSCS");
|
||||
const dev = await m.getDevice(user, secondDeviceId);
|
||||
|
||||
expect(dev).toBeInstanceOf(Device);
|
||||
expect(dev.isVerified()).toStrictEqual(false);
|
||||
@@ -82,7 +112,7 @@ describe(OlmMachine.name, () => {
|
||||
expect(dev.isLocallyTrusted()).toStrictEqual(true);
|
||||
|
||||
expect(dev.userId.toString()).toStrictEqual(user.toString());
|
||||
expect(dev.deviceId.toString()).toStrictEqual(device.toString());
|
||||
expect(dev.deviceId.toString()).toStrictEqual(secondDeviceId.toString());
|
||||
expect(dev.deviceName).toBeUndefined();
|
||||
|
||||
const deviceKey = dev.getKey(DeviceKeyAlgorithmName.Ed25519);
|
||||
@@ -108,18 +138,18 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Key Verification', () => {
|
||||
const userId1 = new UserId('@alice:example.org');
|
||||
const deviceId1 = new DeviceId('alice_device');
|
||||
describe("Key Verification", () => {
|
||||
const userId1 = new UserId("@alice:example.org");
|
||||
const deviceId1 = new DeviceId("alice_device");
|
||||
|
||||
const userId2 = new UserId('@bob:example.org');
|
||||
const deviceId2 = new DeviceId('bob_device');
|
||||
const userId2 = new UserId("@bob:example.org");
|
||||
const deviceId2 = new DeviceId("bob_device");
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || userId1, new_device || deviceId1);
|
||||
return OlmMachine.initialize(new_user || userId1, new_device || deviceId1);
|
||||
}
|
||||
|
||||
describe('SAS', () => {
|
||||
describe("SAS", () => {
|
||||
// First Olm machine.
|
||||
let m1;
|
||||
|
||||
@@ -137,7 +167,7 @@ describe('Key Verification', () => {
|
||||
// The flow ID.
|
||||
let flowId;
|
||||
|
||||
test('can request verification (`m.key.verification.request`)', async () => {
|
||||
test("can request verification (`m.key.verification.request`)", async () => {
|
||||
// Make `m1` and `m2` be aware of each other.
|
||||
{
|
||||
await addMachineToMachine(m2, m1);
|
||||
@@ -164,7 +194,9 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest1.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest1.weStarted()).toStrictEqual(true);
|
||||
@@ -172,17 +204,17 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.request");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
@@ -193,7 +225,7 @@ describe('Key Verification', () => {
|
||||
// Verification request for `m2`.
|
||||
let verificationRequest2;
|
||||
|
||||
test('can fetch received request verification', async () => {
|
||||
test("can fetch received request verification", async () => {
|
||||
// Oh, a new verification request.
|
||||
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
|
||||
|
||||
@@ -207,7 +239,9 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest2.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest2.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest2.flowId).toStrictEqual(flowId);
|
||||
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
|
||||
@@ -220,43 +254,52 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
|
||||
});
|
||||
|
||||
test('can accept a verification request (`m.key.verification.ready`)', async () => {
|
||||
test("can accept a verification request (`m.key.verification.ready`)", async () => {
|
||||
// Accept the verification request.
|
||||
let outgoingVerificationRequest = verificationRequest2.accept();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
// The request verification is ready.
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.ready");
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification ready to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('verification requests are synchronized and automatically updated', () => {
|
||||
test("verification requests are synchronized and automatically updated", () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(true);
|
||||
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
});
|
||||
|
||||
// SAS verification for the second machine.
|
||||
let sas2;
|
||||
|
||||
test('can start a SAS verification (`m.key.verification.start`)', async () => {
|
||||
test("can start a SAS verification (`m.key.verification.start`)", async () => {
|
||||
// Let's start a SAS verification, from `m2` for example.
|
||||
[sas2, outgoingVerificationRequest] = await verificationRequest2.startSas();
|
||||
expect(sas2).toBeInstanceOf(Sas);
|
||||
@@ -283,17 +326,17 @@ describe('Key Verification', () => {
|
||||
expect(sas2.decimals()).toBeUndefined();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the SAS start to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
@@ -302,7 +345,7 @@ describe('Key Verification', () => {
|
||||
// SAS verification for the second machine.
|
||||
let sas1;
|
||||
|
||||
test('can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)', async () => {
|
||||
test("can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)", async () => {
|
||||
// Let's fetch the ongoing SAS verification.
|
||||
sas1 = await m1.getVerification(userId2, flowId);
|
||||
|
||||
@@ -330,81 +373,74 @@ describe('Key Verification', () => {
|
||||
|
||||
// Let's accept thet SAS start request.
|
||||
let outgoingVerificationRequest = sas1.accept();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.accept");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.accept');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the SAS accept to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('emojis are supported by both sides', () => {
|
||||
test("emojis are supported by both sides", () => {
|
||||
expect(sas1.supportsEmoji()).toStrictEqual(true);
|
||||
expect(sas2.supportsEmoji()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('one side sends verification key (`m.key.verification.key`)', async () => {
|
||||
test("one side sends verification key (`m.key.verification.key`)", async () => {
|
||||
// Let's send the verification keys from `m2` to `m1`.
|
||||
const outgoingRequests = await m2.outgoingRequests();
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.key");
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS key to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m2.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
m2.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
|
||||
});
|
||||
|
||||
test('other side sends back verification key (`m.key.verification.key`)', async () => {
|
||||
test("other side sends back verification key (`m.key.verification.key`)", async () => {
|
||||
// Let's send the verification keys from `m1` to `m2`.
|
||||
const outgoingRequests = await m1.outgoingRequests();
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.key");
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS key to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
|
||||
});
|
||||
|
||||
test('emojis match from both sides', () => {
|
||||
test("emojis match from both sides", () => {
|
||||
const emojis1 = sas1.emoji();
|
||||
const emojiIndexes1 = sas1.emojiIndex();
|
||||
const emojis2 = sas2.emoji();
|
||||
@@ -415,9 +451,15 @@ describe('Key Verification', () => {
|
||||
expect(emojis2).toHaveLength(emojis1.length);
|
||||
expect(emojiIndexes2).toHaveLength(emojis1.length);
|
||||
|
||||
const isEmoji = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
|
||||
const isEmoji =
|
||||
/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
|
||||
|
||||
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(emojis1, emojiIndexes1, emojis2, emojiIndexes2)) {
|
||||
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(
|
||||
emojis1,
|
||||
emojiIndexes1,
|
||||
emojis2,
|
||||
emojiIndexes2,
|
||||
)) {
|
||||
expect(emoji1).toBeInstanceOf(Emoji);
|
||||
expect(emoji1.symbol).toMatch(isEmoji);
|
||||
expect(emoji1.description).toBeTruthy();
|
||||
@@ -433,7 +475,7 @@ describe('Key Verification', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('decimals match from both sides', () => {
|
||||
test("decimals match from both sides", () => {
|
||||
const decimals1 = sas1.decimals();
|
||||
const decimals2 = sas2.decimals();
|
||||
|
||||
@@ -449,7 +491,7 @@ describe('Key Verification', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('can confirm keys match (`m.key.verification.mac`)', async () => {
|
||||
test("can confirm keys match (`m.key.verification.mac`)", async () => {
|
||||
// `m1` confirms.
|
||||
const [outgoingVerificationRequests, signatureUploadRequest] = await sas1.confirm();
|
||||
|
||||
@@ -459,23 +501,23 @@ describe('Key Verification', () => {
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[0];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.mac");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS confirmation to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm back keys match (`m.key.verification.done`)', async () => {
|
||||
test("can confirm back keys match (`m.key.verification.done`)", async () => {
|
||||
// `m2` confirms.
|
||||
const [outgoingVerificationRequests, signatureUploadRequest] = await sas2.confirm();
|
||||
|
||||
@@ -487,17 +529,17 @@ describe('Key Verification', () => {
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[0];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.mac");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS confirmation to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
@@ -508,51 +550,47 @@ describe('Key Verification', () => {
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[1];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.done");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS done to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
}
|
||||
});
|
||||
|
||||
test('can send final done (`m.key.verification.done`)', async () => {
|
||||
test("can send final done (`m.key.verification.done`)", async () => {
|
||||
const outgoingRequests = await m1.outgoingRequests();
|
||||
expect(outgoingRequests).toHaveLength(4);
|
||||
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.done");
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS key to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
|
||||
});
|
||||
|
||||
test('can see if verification is done', () => {
|
||||
test("can see if verification is done", () => {
|
||||
expect(verificationRequest1.isDone()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isDone()).toStrictEqual(true);
|
||||
|
||||
@@ -561,10 +599,10 @@ describe('Key Verification', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('QR Code', () => {
|
||||
describe("QR Code", () => {
|
||||
if (undefined === Qr) {
|
||||
// qrcode supports is not enabled
|
||||
console.info('qrcode support is disabled, skip the associated test suite');
|
||||
console.info("qrcode support is disabled, skip the associated test suite");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -586,7 +624,7 @@ describe('Key Verification', () => {
|
||||
// The flow ID.
|
||||
let flowId;
|
||||
|
||||
test('can request verification (`m.key.verification.request`)', async () => {
|
||||
test("can request verification (`m.key.verification.request`)", async () => {
|
||||
// Make `m1` and `m2` be aware of each other.
|
||||
{
|
||||
await addMachineToMachine(m2, m1);
|
||||
@@ -616,7 +654,9 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest1.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest1.weStarted()).toStrictEqual(true);
|
||||
@@ -624,17 +664,17 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.request");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
@@ -645,7 +685,7 @@ describe('Key Verification', () => {
|
||||
// Verification request for `m2`.
|
||||
let verificationRequest2;
|
||||
|
||||
test('can fetch received request verification', async () => {
|
||||
test("can fetch received request verification", async () => {
|
||||
// Oh, a new verification request.
|
||||
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
|
||||
|
||||
@@ -659,7 +699,9 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest2.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest2.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest2.flowId).toStrictEqual(flowId);
|
||||
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
|
||||
@@ -672,7 +714,7 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
|
||||
});
|
||||
|
||||
test('can accept a verification request with methods (`m.key.verification.ready`)', async () => {
|
||||
test("can accept a verification request with methods (`m.key.verification.ready`)", async () => {
|
||||
// Accept the verification request.
|
||||
let outgoingVerificationRequest = verificationRequest2.acceptWithMethods([
|
||||
VerificationMethod.QrCodeScanV1, // by default
|
||||
@@ -682,36 +724,45 @@ describe('Key Verification', () => {
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
// The request verification is ready.
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.ready");
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification ready to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('verification requests are synchronized and automatically updated', () => {
|
||||
test("verification requests are synchronized and automatically updated", () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(true);
|
||||
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
});
|
||||
|
||||
// QR verification for the second machine.
|
||||
let qr2;
|
||||
|
||||
test('can generate a QR code', async () => {
|
||||
test("can generate a QR code", async () => {
|
||||
qr2 = await verificationRequest2.generateQrCode();
|
||||
|
||||
expect(qr2).toBeInstanceOf(Qr);
|
||||
@@ -731,19 +782,19 @@ describe('Key Verification', () => {
|
||||
expect(qr2.roomId).toBeUndefined();
|
||||
});
|
||||
|
||||
let qrCodeBytes;
|
||||
test("can read QR code's bytes", async () => {
|
||||
const qrCodeHeader = "MATRIX";
|
||||
const qrCodeVersion = "\x02";
|
||||
|
||||
test('can read QR code\'s bytes', async () => {
|
||||
const qrCodeHeader = 'MATRIX';
|
||||
const qrCodeVersion = '\x02';
|
||||
|
||||
qrCodeBytes = qr2.toBytes();
|
||||
const qrCodeBytes = qr2.toBytes();
|
||||
|
||||
expect(qrCodeBytes).toHaveLength(122);
|
||||
expect(qrCodeBytes.slice(0, 7)).toStrictEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0)));
|
||||
expect(Array.from(qrCodeBytes.slice(0, 7))).toEqual(
|
||||
[...qrCodeHeader, ...qrCodeVersion].map((char) => char.charCodeAt(0)),
|
||||
);
|
||||
});
|
||||
|
||||
test('can render QR code', async () => {
|
||||
test("can render QR code", async () => {
|
||||
const qrCode = qr2.toQrCode();
|
||||
|
||||
expect(qrCode).toBeInstanceOf(QrCode);
|
||||
@@ -759,7 +810,7 @@ describe('Key Verification', () => {
|
||||
// 45px ⨉ 45px
|
||||
expect(buffer).toHaveLength(45 * 45);
|
||||
// 0 for a white pixel, 1 for a black pixel.
|
||||
expect(buffer.every(p => p == 0 || p == 1)).toStrictEqual(true);
|
||||
expect(buffer.every((p) => p == 0 || p == 1)).toStrictEqual(true);
|
||||
|
||||
/*
|
||||
const { Canvas } = require('canvas');
|
||||
@@ -816,8 +867,8 @@ describe('Key Verification', () => {
|
||||
|
||||
let qr1;
|
||||
|
||||
test('can scan a QR code from bytes', async () => {
|
||||
const scan = QrCodeScan.fromBytes(qrCodeBytes);
|
||||
test("can scan a QR code from bytes", async () => {
|
||||
const scan = QrCodeScan.fromBytes(qr2.toBytes());
|
||||
|
||||
expect(scan).toBeInstanceOf(QrCodeScan);
|
||||
|
||||
@@ -840,58 +891,58 @@ describe('Key Verification', () => {
|
||||
expect(qr1.roomId).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can start a QR verification/reciprocate (`m.key.verification.start`)', async () => {
|
||||
test("can start a QR verification/reciprocate (`m.key.verification.start`)", async () => {
|
||||
let outgoingVerificationRequest = qr1.reciprocate();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm QR code has been scanned', () => {
|
||||
test("can confirm QR code has been scanned", () => {
|
||||
expect(qr2.hasBeenScanned()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can confirm scanning (`m.key.verification.done`)', async () => {
|
||||
test("can confirm scanning (`m.key.verification.done`)", async () => {
|
||||
let outgoingVerificationRequest = qr2.confirmScanning();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.done");
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}]
|
||||
};
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm QR code has been confirmed', () => {
|
||||
test("can confirm QR code has been confirmed", () => {
|
||||
expect(qr2.hasBeenConfirmed()).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationMethod', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("VerificationMethod", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(VerificationMethod.SasV1).toStrictEqual(0);
|
||||
expect(VerificationMethod.QrCodeScanV1).toStrictEqual(1);
|
||||
expect(VerificationMethod.QrCodeShowV1).toStrictEqual(2);
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const {
|
||||
EncryptionAlgorithm,
|
||||
EncryptionSettings,
|
||||
HistoryVisibility,
|
||||
VerificationState,
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe('EncryptionAlgorithm', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("EncryptionAlgorithm", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
|
||||
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptionSettings.name, () => {
|
||||
test('can be instantiated with default values', () => {
|
||||
test("can be instantiated with default values", () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
|
||||
@@ -17,20 +22,14 @@ describe(EncryptionSettings.name, () => {
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
|
||||
});
|
||||
|
||||
test('checks the history visibility values', () => {
|
||||
test("checks the history visibility values", () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
es.historyVisibility = HistoryVisibility.Invited;
|
||||
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
|
||||
expect(() => { es.historyVisibility = 42 }).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationState', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(VerificationState.Trusted).toStrictEqual(0);
|
||||
expect(VerificationState.Untrusted).toStrictEqual(1);
|
||||
expect(VerificationState.UnknownDevice).toStrictEqual(2);
|
||||
expect(() => {
|
||||
es.historyVisibility = 42;
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { HistoryVisibility } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe('HistoryVisibility', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("HistoryVisibility", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(HistoryVisibility.Invited).toStrictEqual(0);
|
||||
expect(HistoryVisibility.Joined).toStrictEqual(1);
|
||||
expect(HistoryVisibility.Shared).toStrictEqual(2);
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
function* zip(...arrays) {
|
||||
const len = Math.min(...arrays.map((array) => array.length));
|
||||
|
||||
for (let nth = 0; nth < len; ++nth) {
|
||||
yield [...arrays.map((array) => array.at(nth))]
|
||||
yield [...arrays.map((array) => array.at(nth))];
|
||||
}
|
||||
}
|
||||
|
||||
// Add a machine to another machine, i.e. be sure a machine knows
|
||||
// another exists.
|
||||
async function addMachineToMachine(machineToAdd, machine) {
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
const receiveSyncChanges = JSON.parse(
|
||||
await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
|
||||
);
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
expect(receiveSyncChanges).toEqual([]);
|
||||
|
||||
const outgoingRequests = await machineToAdd.outgoingRequests();
|
||||
|
||||
@@ -30,6 +32,7 @@ async function addMachineToMachine(machineToAdd, machine) {
|
||||
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
|
||||
expect(outgoingRequests[0].id).toBeDefined();
|
||||
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
|
||||
expect(outgoingRequests[0].body).toBeDefined();
|
||||
|
||||
const body = JSON.parse(outgoingRequests[0].body);
|
||||
expect(body.device_keys).toBeDefined();
|
||||
@@ -37,22 +40,25 @@ async function addMachineToMachine(machineToAdd, machine) {
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
one_time_key_counts: {
|
||||
curve25519: 10,
|
||||
signed_curve25519: 20,
|
||||
},
|
||||
});
|
||||
const marked = await machineToAdd.markRequestAsSent(outgoingRequests[0].id, outgoingRequests[0].type, hypothetical_response);
|
||||
const marked = await machineToAdd.markRequestAsSent(
|
||||
outgoingRequests[0].id,
|
||||
outgoingRequests[0].type,
|
||||
hypothetical_response,
|
||||
);
|
||||
expect(marked).toStrictEqual(true);
|
||||
|
||||
keysUploadRequest = body;
|
||||
keysUploadRequest = outgoingRequests[0];
|
||||
}
|
||||
|
||||
{
|
||||
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
|
||||
|
||||
let [signingKeysUploadRequest, _] = await machineToAdd.bootstrapCrossSigning(true);
|
||||
signingKeysUploadRequest = JSON.parse(signingKeysUploadRequest.body);
|
||||
|
||||
// Let's forge a `KeysQuery`'s response.
|
||||
let keyQueryResponse = {
|
||||
@@ -64,12 +70,18 @@ async function addMachineToMachine(machineToAdd, machine) {
|
||||
const userId = machineToAdd.userId.toString();
|
||||
const deviceId = machineToAdd.deviceId.toString();
|
||||
keyQueryResponse.device_keys[userId] = {};
|
||||
keyQueryResponse.device_keys[userId][deviceId] = keysUploadRequest.device_keys;
|
||||
keyQueryResponse.master_keys[userId] = signingKeysUploadRequest.master_key;
|
||||
keyQueryResponse.self_signing_keys[userId] = signingKeysUploadRequest.self_signing_key;
|
||||
keyQueryResponse.user_signing_keys[userId] = signingKeysUploadRequest.user_signing_key;
|
||||
keyQueryResponse.device_keys[userId][deviceId] = JSON.parse(keysUploadRequest.body).device_keys;
|
||||
|
||||
const marked = await machine.markRequestAsSent(outgoingRequests[1].id, outgoingRequests[1].type, JSON.stringify(keyQueryResponse));
|
||||
const keys = JSON.parse(signingKeysUploadRequest.body);
|
||||
keyQueryResponse.master_keys[userId] = keys.master_key;
|
||||
keyQueryResponse.self_signing_keys[userId] = keys.self_signing_key;
|
||||
keyQueryResponse.user_signing_keys[userId] = keys.user_signing_key;
|
||||
|
||||
const marked = await machine.markRequestAsSent(
|
||||
outgoingRequests[1].id,
|
||||
outgoingRequests[1].type,
|
||||
JSON.stringify(keyQueryResponse),
|
||||
);
|
||||
expect(marked).toStrictEqual(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,65 +7,75 @@ const {
|
||||
RoomId,
|
||||
ServerName,
|
||||
UserId,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe(UserId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new UserId('@foobar') }).toThrow();
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new UserId("@foobar");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
const user = new UserId('@foo:bar.org');
|
||||
const user = new UserId("@foo:bar.org");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(user.localpart).toStrictEqual('foo');
|
||||
test("localpart is present", () => {
|
||||
expect(user.localpart).toStrictEqual("foo");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(user.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('user ID is not historical', () => {
|
||||
test("user ID is not historical", () => {
|
||||
expect(user.isHistorical()).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can read the user ID as a string', () => {
|
||||
expect(user.toString()).toStrictEqual('@foo:bar.org');
|
||||
})
|
||||
test("can read the user ID as a string", () => {
|
||||
expect(user.toString()).toStrictEqual("@foo:bar.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe(DeviceId.name, () => {
|
||||
const device = new DeviceId('foo');
|
||||
const device = new DeviceId("foo");
|
||||
|
||||
test('can read the device ID as a string', () => {
|
||||
expect(device.toString()).toStrictEqual('foo');
|
||||
})
|
||||
test("can read the device ID as a string", () => {
|
||||
expect(device.toString()).toStrictEqual("foo");
|
||||
});
|
||||
});
|
||||
|
||||
describe(DeviceKeyId.name, () => {
|
||||
for (const deviceKey of [
|
||||
{ name: 'ed25519',
|
||||
id: 'ed25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Ed25519,
|
||||
algorithm: 'ed25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "ed25519",
|
||||
id: "ed25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Ed25519,
|
||||
algorithm: "ed25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'curve25519',
|
||||
id: 'curve25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Curve25519,
|
||||
algorithm: 'curve25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "curve25519",
|
||||
id: "curve25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Curve25519,
|
||||
algorithm: "curve25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'signed curve25519',
|
||||
id: 'signed_curve25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
|
||||
algorithm: 'signed_curve25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "signed curve25519",
|
||||
id: "signed_curve25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
|
||||
algorithm: "signed_curve25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'unknown',
|
||||
id: 'hello:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Unknown,
|
||||
algorithm: 'hello',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "unknown",
|
||||
id: "hello:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Unknown,
|
||||
algorithm: "hello",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
]) {
|
||||
test(`${deviceKey.name} algorithm`, () => {
|
||||
const dk = new DeviceKeyId(deviceKey.id);
|
||||
@@ -78,8 +88,8 @@ describe(DeviceKeyId.name, () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('DeviceKeyAlgorithmName', () => {
|
||||
test('has the correct variants', () => {
|
||||
describe("DeviceKeyAlgorithmName", () => {
|
||||
test("has the correct variants", () => {
|
||||
expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0);
|
||||
expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1);
|
||||
expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2);
|
||||
@@ -88,94 +98,100 @@ describe('DeviceKeyAlgorithmName', () => {
|
||||
});
|
||||
|
||||
describe(RoomId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new RoomId('!foo') }).toThrow();
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new RoomId("!foo");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
const room = new RoomId('!foo:bar.org');
|
||||
const room = new RoomId("!foo:bar.org");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('foo');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("foo");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('!foo:bar.org');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("!foo:bar.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe(ServerName.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new ServerName('@foobar') }).toThrow()
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new ServerName("@foobar");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('host is present', () => {
|
||||
expect(new ServerName('foo.org').host).toStrictEqual('foo.org');
|
||||
test("host is present", () => {
|
||||
expect(new ServerName("foo.org").host).toStrictEqual("foo.org");
|
||||
});
|
||||
|
||||
test('port can be optional', () => {
|
||||
expect(new ServerName('foo.org').port).toStrictEqual(undefined);
|
||||
expect(new ServerName('foo.org:1234').port).toStrictEqual(1234);
|
||||
test("port can be optional", () => {
|
||||
expect(new ServerName("foo.org").port).toStrictEqual(undefined);
|
||||
expect(new ServerName("foo.org:1234").port).toStrictEqual(1234);
|
||||
});
|
||||
|
||||
test('server is not an IP literal', () => {
|
||||
expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false);
|
||||
test("server is not an IP literal", () => {
|
||||
expect(new ServerName("foo.org").isIpLiteral()).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EventId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new EventId('%foo') }).toThrow();
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new EventId("%foo");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
describe('Versions 1 & 2', () => {
|
||||
const room = new EventId('$h29iv0s8:foo.org');
|
||||
describe("Versions 1 & 2", () => {
|
||||
const room = new EventId("$h29iv0s8:foo.org");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('h29iv0s8');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("h29iv0s8");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$h29iv0s8:foo.org');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("$h29iv0s8:foo.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe('Version 3', () => {
|
||||
const room = new EventId('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
describe("Version 3", () => {
|
||||
const room = new EventId("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
|
||||
});
|
||||
});
|
||||
|
||||
describe('Version 4', () => {
|
||||
const room = new EventId('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
describe("Version 4", () => {
|
||||
const room = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -5,8 +5,8 @@ const {
|
||||
DeviceKeyId,
|
||||
DeviceLists,
|
||||
EncryptionSettings,
|
||||
EventId,
|
||||
InboundGroupSession,
|
||||
KeysClaimRequest,
|
||||
KeysQueryRequest,
|
||||
KeysUploadRequest,
|
||||
MaybeSignature,
|
||||
@@ -14,55 +14,81 @@ const {
|
||||
OwnUserIdentity,
|
||||
RequestType,
|
||||
RoomId,
|
||||
RoomMessageRequest,
|
||||
ShieldColor,
|
||||
SignatureUploadRequest,
|
||||
ToDeviceRequest,
|
||||
UserId,
|
||||
UserIdentity,
|
||||
VerificationRequest,
|
||||
VerificationState,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { addMachineToMachine } = require('./helper');
|
||||
require('fake-indexeddb/auto');
|
||||
Versions,
|
||||
getVersions,
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
const { addMachineToMachine } = require("./helper");
|
||||
require("fake-indexeddb/auto");
|
||||
|
||||
describe("Versions", () => {
|
||||
test("can find out the crate versions", async () => {
|
||||
const versions = getVersions();
|
||||
|
||||
expect(versions).toBeInstanceOf(Versions);
|
||||
expect(versions.vodozemac).toBeDefined();
|
||||
expect(versions.matrix_sdk_crypto).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
test('can be instantiated with the async initializer', async () => {
|
||||
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
|
||||
test("can be instantiated with the async initializer", async () => {
|
||||
expect(await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"))).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
|
||||
test('can be instantiated with a store', async () => {
|
||||
// No databases.
|
||||
expect(await indexedDB.databases()).toHaveLength(0);
|
||||
test("can be instantiated with a store", async () => {
|
||||
let store_name = "hello";
|
||||
let store_passphrase = "world";
|
||||
|
||||
let store_name = 'hello';
|
||||
let store_passphrase = 'world';
|
||||
const by_store_name = (db) => db.name.startsWith(store_name);
|
||||
|
||||
// No databases.
|
||||
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0);
|
||||
|
||||
// Creating a new Olm machine.
|
||||
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
|
||||
expect(
|
||||
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
|
||||
// Oh, there is 2 databases now, prefixed by `store_name`.
|
||||
let databases = await indexedDB.databases();
|
||||
let databases = (await indexedDB.databases()).filter(by_store_name);
|
||||
|
||||
expect(databases).toHaveLength(2);
|
||||
expect(databases).toStrictEqual([
|
||||
{ name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 },
|
||||
{ name: `${store_name}::matrix-sdk-crypto`, version: 1 },
|
||||
{ name: `${store_name}::matrix-sdk-crypto`, version: 3 },
|
||||
]);
|
||||
|
||||
// Creating a new Olm machine, with the stored state.
|
||||
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
|
||||
expect(
|
||||
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
|
||||
// Same number of databases.
|
||||
expect(await indexedDB.databases()).toHaveLength(2);
|
||||
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe('cannot be instantiated with a store', () => {
|
||||
test('store name is missing', async () => {
|
||||
describe("cannot be instantiated with a store", () => {
|
||||
test("store name is missing", async () => {
|
||||
let store_name = null;
|
||||
let store_passphrase = 'world';
|
||||
let store_passphrase = "world";
|
||||
|
||||
let err = null;
|
||||
|
||||
try {
|
||||
await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
|
||||
await OlmMachine.initialize(
|
||||
new UserId("@foo:bar.org"),
|
||||
new DeviceId("baz"),
|
||||
store_name,
|
||||
store_passphrase,
|
||||
);
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
@@ -70,14 +96,19 @@ describe(OlmMachine.name, () => {
|
||||
expect(err).toBeDefined();
|
||||
});
|
||||
|
||||
test('store passphrase is missing', async () => {
|
||||
let store_name = 'hello';
|
||||
test("store passphrase is missing", async () => {
|
||||
let store_name = "hello";
|
||||
let store_passphrase = null;
|
||||
|
||||
let err = null;
|
||||
|
||||
try {
|
||||
await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
|
||||
await OlmMachine.initialize(
|
||||
new UserId("@foo:bar.org"),
|
||||
new DeviceId("baz"),
|
||||
store_name,
|
||||
store_passphrase,
|
||||
);
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
@@ -86,68 +117,121 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("foobar");
|
||||
const room = new RoomId("!baz:matrix.org");
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || user, new_device || device);
|
||||
return OlmMachine.initialize(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can read user ID', async () => {
|
||||
test("can drop/close", async () => {
|
||||
m = await machine();
|
||||
m.close();
|
||||
});
|
||||
|
||||
test("can drop/close with a store", async () => {
|
||||
let store_name = "temporary";
|
||||
let store_passphrase = "temporary";
|
||||
|
||||
const by_store_name = (db) => db.name.startsWith(store_name);
|
||||
|
||||
// No databases.
|
||||
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0);
|
||||
|
||||
// Creating a new Olm machine.
|
||||
const m = await OlmMachine.initialize(
|
||||
new UserId("@foo:bar.org"),
|
||||
new DeviceId("baz"),
|
||||
store_name,
|
||||
store_passphrase,
|
||||
);
|
||||
expect(m).toBeInstanceOf(OlmMachine);
|
||||
|
||||
// Oh, there is 2 databases now, prefixed by `store_name`.
|
||||
let databases = (await indexedDB.databases()).filter(by_store_name);
|
||||
|
||||
expect(databases).toHaveLength(2);
|
||||
expect(databases).toStrictEqual([
|
||||
{ name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 },
|
||||
{ name: `${store_name}::matrix-sdk-crypto`, version: 3 },
|
||||
]);
|
||||
|
||||
// Let's force to close the `OlmMachine`.
|
||||
m.close();
|
||||
|
||||
// Now we can delete the databases!
|
||||
for (const database_name of [`${store_name}::matrix-sdk-crypto`, `${store_name}::matrix-sdk-crypto-meta`]) {
|
||||
const deleting = indexedDB.deleteDatabase(database_name);
|
||||
deleting.onsuccess = () => {};
|
||||
deleting.onerror = () => {
|
||||
throw new Error("failed to remove the database (error)");
|
||||
};
|
||||
deleting.onblocked = () => {
|
||||
throw new Error("failed to remove the database (blocked)");
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
test("can read user ID", async () => {
|
||||
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
|
||||
});
|
||||
|
||||
test('can read device ID', async () => {
|
||||
test("can read device ID", async () => {
|
||||
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
|
||||
});
|
||||
|
||||
test('can read identity keys', async () => {
|
||||
test("can read identity keys", async () => {
|
||||
const identityKeys = (await machine()).identityKeys;
|
||||
|
||||
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
});
|
||||
|
||||
test('can read display name', async () => {
|
||||
test("can read display name", async () => {
|
||||
expect(await machine().displayName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read tracked users', async () => {
|
||||
const trackedUsers = (await machine()).trackedUsers();
|
||||
test("can read tracked users", async () => {
|
||||
const m = await machine();
|
||||
const trackedUsers = await m.trackedUsers();
|
||||
|
||||
expect(trackedUsers).toBeInstanceOf(Set);
|
||||
expect(trackedUsers.size).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('can update tracked users', async () => {
|
||||
test("can update tracked users", async () => {
|
||||
const m = await machine();
|
||||
|
||||
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
test('can receive sync changes', async () => {
|
||||
test("can receive sync changes", async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
const receiveSyncChanges = JSON.parse(
|
||||
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
|
||||
);
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
expect(receiveSyncChanges).toEqual([]);
|
||||
});
|
||||
|
||||
test('can get the outgoing requests that need to be send out', async () => {
|
||||
test("can get the outgoing requests that need to be send out", async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
const receiveSyncChanges = JSON.parse(
|
||||
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
|
||||
);
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
expect(receiveSyncChanges).toEqual([]);
|
||||
|
||||
const outgoingRequests = await m.outgoingRequests();
|
||||
|
||||
@@ -157,6 +241,7 @@ describe(OlmMachine.name, () => {
|
||||
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
|
||||
expect(outgoingRequests[0].id).toBeDefined();
|
||||
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
|
||||
expect(outgoingRequests[0].body).toBeDefined();
|
||||
|
||||
const body = JSON.parse(outgoingRequests[0].body);
|
||||
expect(body.device_keys).toBeDefined();
|
||||
@@ -167,6 +252,7 @@ describe(OlmMachine.name, () => {
|
||||
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
|
||||
expect(outgoingRequests[1].id).toBeDefined();
|
||||
expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery);
|
||||
expect(outgoingRequests[1].body).toBeDefined();
|
||||
|
||||
const body = JSON.parse(outgoingRequests[1].body);
|
||||
expect(body.timeout).toBeDefined();
|
||||
@@ -175,35 +261,40 @@ describe(OlmMachine.name, () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('setup workflow to mark requests as sent', () => {
|
||||
describe("setup workflow to mark requests as sent", () => {
|
||||
let m;
|
||||
let ougoingRequests;
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
|
||||
m = await machine(new UserId("@alice:example.org"), new DeviceId("DEVICEID"));
|
||||
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys);
|
||||
const receiveSyncChanges = await m.receiveSyncChanges(
|
||||
toDeviceEvents,
|
||||
changedDevices,
|
||||
oneTimeKeyCounts,
|
||||
unusedFallbackKeys,
|
||||
);
|
||||
outgoingRequests = await m.outgoingRequests();
|
||||
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('can mark requests as sent', async () => {
|
||||
test("can mark requests as sent", async () => {
|
||||
{
|
||||
const request = outgoingRequests[0];
|
||||
expect(request).toBeInstanceOf(KeysUploadRequest);
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
one_time_key_counts: {
|
||||
curve25519: 10,
|
||||
signed_curve25519: 20,
|
||||
},
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
@@ -215,31 +306,29 @@ describe(OlmMachine.name, () => {
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
device_keys: {
|
||||
"@alice:example.org": {
|
||||
"JLAFKJWSCS": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"keys": {
|
||||
JLAFKJWSCS: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "JLAFKJWSCS",
|
||||
keys: {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
||||
}
|
||||
"ed25519:JLAFKJWSCS":
|
||||
"m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA",
|
||||
},
|
||||
},
|
||||
"unsigned": {
|
||||
"device_display_name": "Alice's mobile phone"
|
||||
unsigned: {
|
||||
device_display_name: "Alice's mobile phone",
|
||||
},
|
||||
"user_id": "@alice:example.org"
|
||||
}
|
||||
}
|
||||
user_id: "@alice:example.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
"failures": {}
|
||||
failures: {},
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
@@ -247,143 +336,145 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup workflow to encrypt/decrypt events', () => {
|
||||
describe("setup workflow to encrypt/decrypt events", () => {
|
||||
let m;
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('JLAFKJWSCS');
|
||||
const room = new RoomId('!test:localhost');
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("JLAFKJWSCS");
|
||||
const room = new RoomId("!test:localhost");
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(user, device);
|
||||
});
|
||||
|
||||
test('can pass keysquery and keysclaim requests directly', async () => {
|
||||
|
||||
test("can pass keysquery and keysclaim requests directly", async () => {
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
device_keys: {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "AFGUOBTZWM",
|
||||
"keys": {
|
||||
AFGUOBTZWM: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "AFGUOBTZWM",
|
||||
keys: {
|
||||
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
|
||||
}
|
||||
"ed25519:AFGUOBTZWM":
|
||||
"RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA",
|
||||
},
|
||||
},
|
||||
user_id: "@example:localhost",
|
||||
unsigned: {
|
||||
device_display_name: "rust-sdk",
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "rust-sdk"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"failures": {},
|
||||
"master_keys": {
|
||||
failures: {},
|
||||
master_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["master"],
|
||||
keys: {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:TCSJXPWGVS":
|
||||
"+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"self_signing_keys": {
|
||||
self_signing_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["self_signing"],
|
||||
keys: {
|
||||
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI":
|
||||
"kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"user_signing_keys": {
|
||||
user_signing_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["user_signing"],
|
||||
keys: {
|
||||
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s":
|
||||
"g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
|
||||
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
|
||||
}
|
||||
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_keys": {
|
||||
one_time_keys: {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
AFGUOBTZWM: {
|
||||
"signed_curve25519:AAAABQ": {
|
||||
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
"signatures": {
|
||||
key: "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:AFGUOBTZWM":
|
||||
"2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"failures": {}
|
||||
failures: {},
|
||||
});
|
||||
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
|
||||
const marked = await m.markRequestAsSent("bar", RequestType.KeysClaim, hypothetical_response);
|
||||
}
|
||||
});
|
||||
|
||||
test('can share a room key', async () => {
|
||||
const other_users = [new UserId('@example:localhost')];
|
||||
test("can share a room key", async () => {
|
||||
const other_users = [new UserId("@example:localhost")];
|
||||
|
||||
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
|
||||
const requests = await m.shareRoomKey(room, other_users, new EncryptionSettings());
|
||||
|
||||
expect(requests).toHaveLength(1);
|
||||
expect(requests[0].event_type).toBeDefined();
|
||||
expect(requests[0]).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(requests[0].event_type).toEqual("m.room.encrypted");
|
||||
expect(requests[0].txn_id).toBeDefined();
|
||||
expect(requests[0].messages).toBeDefined();
|
||||
expect(requests[0].messages['@example:localhost']).toBeDefined();
|
||||
const content = JSON.parse(requests[0].body);
|
||||
expect(Object.keys(content.messages)).toEqual(["@example:localhost"]);
|
||||
});
|
||||
|
||||
let encrypted;
|
||||
|
||||
test('can encrypt an event', async () => {
|
||||
encrypted = JSON.parse(await m.encryptRoomEvent(
|
||||
room,
|
||||
'm.room.message',
|
||||
JSON.stringify({
|
||||
"msgtype": "m.text",
|
||||
"body": "Hello, World!"
|
||||
}),
|
||||
));
|
||||
test("can encrypt an event", async () => {
|
||||
encrypted = JSON.parse(
|
||||
await m.encryptRoomEvent(
|
||||
room,
|
||||
"m.room.message",
|
||||
JSON.stringify({
|
||||
msgtype: "m.text",
|
||||
body: "Hello, World!",
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(encrypted.algorithm).toBeDefined();
|
||||
expect(encrypted.ciphertext).toBeDefined();
|
||||
@@ -392,17 +483,17 @@ describe(OlmMachine.name, () => {
|
||||
expect(encrypted.session_id).toBeDefined();
|
||||
});
|
||||
|
||||
test('can decrypt an event', async () => {
|
||||
test("can decrypt an event", async () => {
|
||||
const decrypted = await m.decryptRoomEvent(
|
||||
JSON.stringify({
|
||||
"type": "m.room.encrypted",
|
||||
"event_id": "$xxxxx:example.org",
|
||||
"origin_server_ts": Date.now(),
|
||||
"sender": user.toString(),
|
||||
type: "m.room.encrypted",
|
||||
event_id: "$xxxxx:example.org",
|
||||
origin_server_ts: Date.now(),
|
||||
sender: user.toString(),
|
||||
content: encrypted,
|
||||
unsigned: {
|
||||
"age": 1234
|
||||
}
|
||||
age: 1234,
|
||||
},
|
||||
}),
|
||||
room,
|
||||
);
|
||||
@@ -418,11 +509,27 @@ describe(OlmMachine.name, () => {
|
||||
expect(decrypted.senderCurve25519Key).toBeDefined();
|
||||
expect(decrypted.senderClaimedEd25519Key).toBeDefined();
|
||||
expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0);
|
||||
expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted);
|
||||
expect(decrypted.shieldState(true).color).toStrictEqual(ShieldColor.Red);
|
||||
expect(decrypted.shieldState(false).color).toStrictEqual(ShieldColor.Red);
|
||||
});
|
||||
});
|
||||
|
||||
test('can read cross-signing status', async () => {
|
||||
test("failure to decrypt returns a valid error", async () => {
|
||||
const m = await machine();
|
||||
const evt = {
|
||||
type: "m.room.encrypted",
|
||||
event_id: "$xxxxx:example.org",
|
||||
origin_server_ts: Date.now(),
|
||||
sender: user.toString(),
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
ciphertext: "blah",
|
||||
},
|
||||
};
|
||||
await expect(() => m.decryptRoomEvent(JSON.stringify(evt), room)).rejects.toThrowError();
|
||||
});
|
||||
|
||||
test("can read cross-signing status", async () => {
|
||||
const m = await machine();
|
||||
const crossSigningStatus = await m.crossSigningStatus();
|
||||
|
||||
@@ -432,9 +539,9 @@ describe(OlmMachine.name, () => {
|
||||
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can sign a message', async () => {
|
||||
test("can sign a message", async () => {
|
||||
const m = await machine();
|
||||
const signatures = await m.sign('foo');
|
||||
const signatures = await m.sign("foo");
|
||||
|
||||
expect(signatures.isEmpty()).toStrictEqual(false);
|
||||
expect(signatures.count).toStrictEqual(1);
|
||||
@@ -445,9 +552,9 @@ describe(OlmMachine.name, () => {
|
||||
{
|
||||
const signature = signatures.get(user);
|
||||
|
||||
expect(signature.has('ed25519:foobar')).toStrictEqual(true);
|
||||
expect(signature.has("ed25519:foobar")).toStrictEqual(true);
|
||||
|
||||
const s = signature.get('ed25519:foobar');
|
||||
const s = signature.get("ed25519:foobar");
|
||||
|
||||
expect(s).toBeInstanceOf(MaybeSignature);
|
||||
|
||||
@@ -463,18 +570,18 @@ describe(OlmMachine.name, () => {
|
||||
|
||||
// `getSignature`
|
||||
{
|
||||
const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar'));
|
||||
const signature = signatures.getSignature(user, new DeviceKeyId("ed25519:foobar"));
|
||||
expect(signature.toBase64()).toStrictEqual(base64);
|
||||
}
|
||||
|
||||
// Unknown signatures.
|
||||
{
|
||||
expect(signatures.get(new UserId('@hello:example.org'))).toBeUndefined();
|
||||
expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeUndefined();
|
||||
expect(signatures.get(new UserId("@hello:example.org"))).toBeUndefined();
|
||||
expect(signatures.getSignature(user, new DeviceKeyId("world:foobar"))).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test('can get a user identities', async () => {
|
||||
test("can get a user identities", async () => {
|
||||
const m = await machine();
|
||||
let _ = m.bootstrapCrossSigning(true);
|
||||
|
||||
@@ -494,16 +601,16 @@ describe(OlmMachine.name, () => {
|
||||
expect(isTrusted).toStrictEqual(false);
|
||||
});
|
||||
|
||||
describe('can export/import room keys', () => {
|
||||
let m;
|
||||
describe("can export/import room keys", () => {
|
||||
let exportedRoomKeys;
|
||||
|
||||
test('can export room keys', async () => {
|
||||
m = await machine();
|
||||
await m.shareRoomKey(room, [new UserId('@bob:example.org')], new EncryptionSettings());
|
||||
test("can export room keys", async () => {
|
||||
let m = await machine();
|
||||
await m.shareRoomKey(room, [new UserId("@bob:example.org")], new EncryptionSettings());
|
||||
|
||||
exportedRoomKeys = await m.exportRoomKeys(session => {
|
||||
exportedRoomKeys = await m.exportRoomKeys((session) => {
|
||||
expect(session).toBeInstanceOf(InboundGroupSession);
|
||||
expect(session.senderKey.toBase64()).toEqual(m.identityKeys.curve25519.toBase64());
|
||||
expect(session.roomId.toString()).toStrictEqual(room.toString());
|
||||
expect(session.sessionId).toBeDefined();
|
||||
expect(session.hasBeenImported()).toStrictEqual(false);
|
||||
@@ -527,9 +634,9 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
|
||||
let encryptedExportedRoomKeys;
|
||||
let encryptionPassphrase = 'Hello, Matrix!';
|
||||
let encryptionPassphrase = "Hello, Matrix!";
|
||||
|
||||
test('can encrypt the exported room keys', () => {
|
||||
test("can encrypt the exported room keys", () => {
|
||||
encryptedExportedRoomKeys = OlmMachine.encryptExportedRoomKeys(
|
||||
exportedRoomKeys,
|
||||
encryptionPassphrase,
|
||||
@@ -539,7 +646,7 @@ describe(OlmMachine.name, () => {
|
||||
expect(encryptedExportedRoomKeys).toMatch(/^-----BEGIN MEGOLM SESSION DATA-----/);
|
||||
});
|
||||
|
||||
test('can decrypt the exported room keys', () => {
|
||||
test("can decrypt the exported room keys", () => {
|
||||
const decryptedExportedRoomKeys = OlmMachine.decryptExportedRoomKeys(
|
||||
encryptedExportedRoomKeys,
|
||||
encryptionPassphrase,
|
||||
@@ -548,7 +655,7 @@ describe(OlmMachine.name, () => {
|
||||
expect(decryptedExportedRoomKeys).toStrictEqual(exportedRoomKeys);
|
||||
});
|
||||
|
||||
test('can import room keys', async () => {
|
||||
test("can import room keys", async () => {
|
||||
const progressListener = (progress, total) => {
|
||||
expect(progress).toBeLessThan(total);
|
||||
|
||||
@@ -557,6 +664,7 @@ describe(OlmMachine.name, () => {
|
||||
expect(total).toStrictEqual(1n);
|
||||
};
|
||||
|
||||
let m = await machine();
|
||||
const result = JSON.parse(await m.importRoomKeys(exportedRoomKeys, progressListener));
|
||||
|
||||
expect(result).toMatchObject({
|
||||
@@ -565,5 +673,200 @@ describe(OlmMachine.name, () => {
|
||||
keys: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
test("importing room keys calls RoomKeyUpdatedCallback", async () => {
|
||||
const callback = jest.fn();
|
||||
callback.mockImplementation(() => Promise.resolve(undefined));
|
||||
let m = await machine();
|
||||
m.registerRoomKeyUpdatedCallback(callback);
|
||||
await m.importRoomKeys(exportedRoomKeys, (_, _1) => {});
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
let keyInfoList = callback.mock.calls[0][0];
|
||||
expect(keyInfoList.length).toEqual(1);
|
||||
expect(keyInfoList[0].roomId.toString()).toStrictEqual(room.toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe("can do in-room verification", () => {
|
||||
let m;
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("JLAFKJWSCS");
|
||||
const room = new RoomId("!test:localhost");
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(user, device);
|
||||
});
|
||||
|
||||
test("can inject devices from someone else", async () => {
|
||||
{
|
||||
const hypothetical_response = JSON.stringify({
|
||||
device_keys: {
|
||||
"@example:morpheus.localhost": {
|
||||
ATRLDCRXAC: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "ATRLDCRXAC",
|
||||
keys: {
|
||||
"curve25519:ATRLDCRXAC": "cAVT5Es3Z3F5pFD+2w3HT7O9+R3PstzYVkzD51X/FWQ",
|
||||
"ed25519:ATRLDCRXAC": "V2w/T/x7i7AXiCCtS6JldrpbvRliRoef3CqTUNqMRHA",
|
||||
},
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:ATRLDCRXAC":
|
||||
"ro2BjO5J6089B/JOANHnFmGrogrC2TIdMlgJbJO00DjOOcGxXfvOezCFIORTwZNHvkHU617YIGl/4keTDIWvBQ",
|
||||
},
|
||||
},
|
||||
user_id: "@example:morpheus.localhost",
|
||||
unsigned: {
|
||||
device_display_name: "Element Desktop: Linux",
|
||||
},
|
||||
},
|
||||
EYYGYTCTNC: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "EYYGYTCTNC",
|
||||
keys: {
|
||||
"curve25519:EYYGYTCTNC": "Pqu50fo472wgb6NjKkaUxjuqoAIEAmhln2gw/zSQ7Ek",
|
||||
"ed25519:EYYGYTCTNC": "Pf/2QPvui8lDty6TCTglVPRVM+irNHYavNNkyv5yFpU",
|
||||
},
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:EYYGYTCTNC":
|
||||
"pnP5BYLEUUaxDgrvdzCznkjNDbvY1/MFBr1JejdnLiXlcmxRULQpIWZUCO7QTbULsCwMsYQNGn50nfmjBQX3CQ",
|
||||
},
|
||||
},
|
||||
user_id: "@example:morpheus.localhost",
|
||||
unsigned: {
|
||||
device_display_name: "WeeChat-Matrix-rs",
|
||||
},
|
||||
},
|
||||
SUMODVLSIU: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "SUMODVLSIU",
|
||||
keys: {
|
||||
"curve25519:SUMODVLSIU": "geQXWGWc++gcUHk0JcFmEVSjyzDOnk2mjVsUQwbNqQU",
|
||||
"ed25519:SUMODVLSIU": "ccktaQ3g+B18E6FwVhTBYie26OlHbvDUzDEtxOQ4Qcs",
|
||||
},
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:SUMODVLSIU":
|
||||
"Yn+AOxHRt1GQpY2xT2Jcqqn8jh5+Vw23ctA7NXyDiWPsLPLNTpjGWHMjZdpUqflQvpiKfhODPICoIa7Pu0iSAg",
|
||||
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM":
|
||||
"Cio6k/sq289XNTOvTCWre7Q6zg+A3euzMUe7Uy1T3gPqYFzX+kt7EAxrhbPqx1HyXAEz9zD0D/uw9VEXFCvWBQ",
|
||||
},
|
||||
},
|
||||
user_id: "@example:morpheus.localhost",
|
||||
unsigned: {
|
||||
device_display_name: "Element Desktop (Linux)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
failures: {},
|
||||
master_keys: {
|
||||
"@example:morpheus.localhost": {
|
||||
user_id: "@example:morpheus.localhost",
|
||||
usage: ["master"],
|
||||
keys: {
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
|
||||
"ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A",
|
||||
},
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:SUMODVLSIU":
|
||||
"RL6WOuuzB/mZ+edfUFG/KeEcmKh+NaWpM6m2bUYmDnJrtTCYyoU+pgHJuL2/6nynemmONo18JEHBuqtNcMq2AQ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
self_signing_keys: {
|
||||
"@example:morpheus.localhost": {
|
||||
user_id: "@example:morpheus.localhost",
|
||||
usage: ["self_signing"],
|
||||
keys: {
|
||||
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM":
|
||||
"rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM",
|
||||
},
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
|
||||
"uCBn9rpeg6umY8H97ejN26UMp6QDwNL98869t1DoVGL50J8adLN05OZd8lYk9QzwTr2d56ZTGYSYX8kv28SDDA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user_signing_keys: {
|
||||
"@example:morpheus.localhost": {
|
||||
user_id: "@example:morpheus.localhost",
|
||||
usage: ["user_signing"],
|
||||
keys: {
|
||||
"ed25519:GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw":
|
||||
"GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw",
|
||||
},
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
|
||||
"4fIyWlVzuz1pgoegNLZASycORXqKycVS0dNq5vmmwsVEudp1yrPhndnaIJ3fjF8LDHvwzXTvohOid7DiU1j0AA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
|
||||
}
|
||||
});
|
||||
|
||||
test("can start an in-room SAS verification", async () => {
|
||||
let _ = m.bootstrapCrossSigning(true);
|
||||
const identity = await m.getIdentity(new UserId("@example:morpheus.localhost"));
|
||||
|
||||
expect(identity).toBeInstanceOf(UserIdentity);
|
||||
expect(identity.isVerified()).toStrictEqual(false);
|
||||
|
||||
const eventId = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
|
||||
const verificationRequest = await identity.requestVerification(room, eventId);
|
||||
expect(verificationRequest).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
await m.receiveVerificationEvent(
|
||||
JSON.stringify({
|
||||
sender: "@example:morpheus.localhost",
|
||||
type: "m.key.verification.ready",
|
||||
event_id: "$QguWmaeMt6Hao7Ea6XHDInvr8ndknev79t9a2eBxlz0",
|
||||
origin_server_ts: 1674037263075,
|
||||
content: {
|
||||
"methods": ["m.sas.v1", "m.qr_code.show.v1", "m.reciprocate.v1"],
|
||||
"from_device": "SUMODVLSIU",
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: eventId.toString(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
room,
|
||||
);
|
||||
|
||||
expect(verificationRequest.roomId.toString()).toStrictEqual(room.toString());
|
||||
|
||||
const [_sas, outgoingVerificationRequest] = await verificationRequest.startSas();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(RoomMessageRequest);
|
||||
expect(outgoingVerificationRequest.id).toBeDefined();
|
||||
expect(outgoingVerificationRequest.room_id).toStrictEqual(room.toString());
|
||||
expect(outgoingVerificationRequest.txn_id).toBeDefined();
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
|
||||
expect(outgoingVerificationRequest.body).toBeDefined();
|
||||
|
||||
const body = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(body).toMatchObject({
|
||||
"from_device": expect.any(String),
|
||||
"method": "m.sas.v1",
|
||||
"key_agreement_protocols": [expect.any(String)],
|
||||
"hashes": [expect.any(String)],
|
||||
"message_authentication_codes": [expect.any(String), expect.any(String)],
|
||||
"short_authentication_string": ["decimal", "emoji"],
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: eventId.toString(),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const {
|
||||
RequestType,
|
||||
KeysUploadRequest,
|
||||
KeysQueryRequest,
|
||||
KeysClaimRequest,
|
||||
ToDeviceRequest,
|
||||
SignatureUploadRequest,
|
||||
RoomMessageRequest,
|
||||
KeysBackupRequest,
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe('RequestType', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("RequestType", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(RequestType.KeysUpload).toStrictEqual(0);
|
||||
expect(RequestType.KeysQuery).toStrictEqual(1);
|
||||
expect(RequestType.KeysClaim).toStrictEqual(2);
|
||||
@@ -11,25 +20,3 @@ describe('RequestType', () => {
|
||||
expect(RequestType.KeysBackup).toStrictEqual(6);
|
||||
});
|
||||
});
|
||||
|
||||
for (const [request, requestType] of [
|
||||
[KeysUploadRequest, RequestType.KeysUpload],
|
||||
[KeysQueryRequest, RequestType.KeysQuery],
|
||||
[KeysClaimRequest, RequestType.KeysClaim],
|
||||
[ToDeviceRequest, RequestType.ToDevice],
|
||||
[SignatureUploadRequest, RequestType.SignatureUpload],
|
||||
[RoomMessageRequest, RequestType.RoomMessage],
|
||||
[KeysBackupRequest, RequestType.KeysBackup],
|
||||
]) {
|
||||
describe(request.name, () => {
|
||||
test('can be instantiated', () => {
|
||||
const r = new (request)('foo', '{"bar": "baz"}');
|
||||
|
||||
expect(r).toBeInstanceOf(request);
|
||||
expect(r.id).toStrictEqual('foo');
|
||||
expect(r.body).toStrictEqual('{"bar": "baz"}');
|
||||
expect(r.type).toStrictEqual(requestType);
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { DeviceLists, UserId } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe(DeviceLists.name, () => {
|
||||
test('can be empty', () => {
|
||||
test("can be empty", () => {
|
||||
const empty = new DeviceLists();
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
@@ -9,7 +9,7 @@ describe(DeviceLists.name, () => {
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('can be coerced empty', () => {
|
||||
test("can be coerced empty", () => {
|
||||
const empty = new DeviceLists([], []);
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
@@ -17,15 +17,15 @@ describe(DeviceLists.name, () => {
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('returns the correct `changed` and `left`', () => {
|
||||
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
|
||||
test("returns the correct `changed` and `left`", () => {
|
||||
const list = new DeviceLists([new UserId("@foo:bar.org")], [new UserId("@baz:qux.org")]);
|
||||
|
||||
expect(list.isEmpty()).toStrictEqual(false);
|
||||
|
||||
expect(list.changed).toHaveLength(1);
|
||||
expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org');
|
||||
expect(list.changed[0].toString()).toStrictEqual("@foo:bar.org");
|
||||
|
||||
expect(list.left).toHaveLength(1);
|
||||
expect(list.left[0].toString()).toStrictEqual('@baz:qux.org');
|
||||
expect(list.left[0].toString()).toStrictEqual("@baz:qux.org");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe('LoggerLevel', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("LoggerLevel", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(LoggerLevel.Trace).toStrictEqual(0);
|
||||
expect(LoggerLevel.Debug).toStrictEqual(1);
|
||||
expect(LoggerLevel.Info).toStrictEqual(2);
|
||||
@@ -14,7 +14,7 @@ describe(Tracing.name, () => {
|
||||
if (Tracing.isAvailable()) {
|
||||
let tracing = new Tracing(LoggerLevel.Debug);
|
||||
|
||||
test('can installed several times', () => {
|
||||
test("can installed several times", () => {
|
||||
new Tracing(LoggerLevel.Debug);
|
||||
new Tracing(LoggerLevel.Warn);
|
||||
new Tracing(LoggerLevel.Debug);
|
||||
@@ -23,27 +23,30 @@ describe(Tracing.name, () => {
|
||||
const originalConsoleDebug = console.debug;
|
||||
|
||||
for (const [testName, testPreState, testPostState, expectedGotcha] of [
|
||||
["can log something", () => {}, () => {}, true],
|
||||
[
|
||||
'can log something',
|
||||
() => {},
|
||||
() => {},
|
||||
true,
|
||||
],
|
||||
[
|
||||
'can change the logger level',
|
||||
() => { tracing.minLevel = LoggerLevel.Warn },
|
||||
() => { tracing.minLevel = LoggerLevel.Debug },
|
||||
"can change the logger level",
|
||||
() => {
|
||||
tracing.minLevel = LoggerLevel.Warn;
|
||||
},
|
||||
() => {
|
||||
tracing.minLevel = LoggerLevel.Debug;
|
||||
},
|
||||
false,
|
||||
],
|
||||
[
|
||||
'can be turned off',
|
||||
() => { tracing.turnOff() },
|
||||
"can be turned off",
|
||||
() => {
|
||||
tracing.turnOff();
|
||||
},
|
||||
() => {},
|
||||
false,
|
||||
],
|
||||
[
|
||||
'can be turned on',
|
||||
() => { tracing.turnOn() },
|
||||
"can be turned on",
|
||||
() => {
|
||||
tracing.turnOn();
|
||||
},
|
||||
() => {},
|
||||
true,
|
||||
],
|
||||
@@ -51,8 +54,10 @@ describe(Tracing.name, () => {
|
||||
// This one *must* be the last. We are turning tracing off
|
||||
// again for the other tests.
|
||||
[
|
||||
'can be turned off',
|
||||
() => { tracing.turnOff() },
|
||||
"can be turned off",
|
||||
() => {
|
||||
tracing.turnOff();
|
||||
},
|
||||
() => {},
|
||||
false,
|
||||
],
|
||||
@@ -68,7 +73,7 @@ describe(Tracing.name, () => {
|
||||
};
|
||||
|
||||
// Do something that emits a `DEBUG` log.
|
||||
await new OlmMachine(new UserId('@alice:example.org'), new DeviceId('foo'));
|
||||
await OlmMachine.initialize(new UserId("@alice:example.org"), new DeviceId("foo"));
|
||||
|
||||
console.debug = originalConsoleDebug;
|
||||
testPostState();
|
||||
@@ -77,8 +82,10 @@ describe(Tracing.name, () => {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
test('cannot be constructed', () => {
|
||||
expect(() => { new Tracing(LoggerLevel.Error) }).toThrow();
|
||||
test("cannot be constructed", () => {
|
||||
expect(() => {
|
||||
new Tracing(LoggerLevel.Error);
|
||||
}).toThrow();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"typedocOptions": {
|
||||
"entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"],
|
||||
"out": "docs",
|
||||
"readme": "README.md",
|
||||
"readme": "README.md"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
/index.d.ts
|
||||
/matrix-sdk-crypto.*.node
|
||||
/docs/*
|
||||
*.tgz
|
||||
*.tgz
|
||||
|
||||
@@ -5,4 +5,3 @@ build.rs
|
||||
*.node
|
||||
*.tgz
|
||||
tsconfig.json
|
||||
cliff.toml
|
||||
@@ -0,0 +1 @@
|
||||
/pkg
|
||||
@@ -0,0 +1,9 @@
|
||||
// prettier configuration: the same as the conventions used throughout Matrix.org
|
||||
// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js
|
||||
|
||||
module.exports = {
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
quoteProps: "consistent",
|
||||
trailingComma: "all",
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 0.1.0-beta.1 - 2022-07-14
|
||||
|
||||
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
|
||||
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
|
||||
|
||||
## 0.1.0-beta.0 - 2022-07-12
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ license = "Apache-2.0"
|
||||
name = "matrix-sdk-crypto-nodejs"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version = "1.60"
|
||||
rust-version = { workspace = true }
|
||||
version = "0.6.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
@@ -18,7 +18,8 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["bundled-sqlite"]
|
||||
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
tracing = ["dep:tracing-subscriber"]
|
||||
|
||||
@@ -26,17 +27,15 @@ tracing = ["dep:tracing-subscriber"]
|
||||
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
|
||||
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
|
||||
matrix-sdk-sled = { version = "0.2.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
|
||||
ruma = { version = "0.7.0", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
matrix-sdk-sqlite = { version = "0.1.0", path = "../../crates/matrix-sdk-sqlite", features = ["crypto-store"] }
|
||||
ruma = { workspace = true, features = ["rand", "unstable-msc2677"] }
|
||||
napi = { version = "2.9.1", default-features = false, features = ["napi6", "tokio_rt"] }
|
||||
napi-derive = "2.9.1"
|
||||
serde_json = "1.0.79"
|
||||
http = "0.2.6"
|
||||
zeroize = "1.3.0"
|
||||
serde_json = { workspace = true }
|
||||
http = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["tracing-log", "time", "smallvec", "fmt", "env-filter"], optional = true }
|
||||
|
||||
[dependencies.vodozemac]
|
||||
version = "0.3.0"
|
||||
features = ["js"]
|
||||
vodozemac = { workspace = true, features = ["js"] }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.0.0"
|
||||
|
||||
@@ -12,6 +12,7 @@ Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
|
||||
## Usage
|
||||
|
||||
Just add the latest release to your `package.json`:
|
||||
|
||||
```sh
|
||||
$ npm install --save @matrix-org/matrix-sdk-crypto-nodejs
|
||||
```
|
||||
@@ -112,35 +113,34 @@ generated. At the same level of those files, you can edit a file and
|
||||
try this:
|
||||
|
||||
```javascript
|
||||
const { OlmMachine } = require('./index.js');
|
||||
const { OlmMachine } = require("./index.js");
|
||||
|
||||
// Let's see what we can do.
|
||||
```
|
||||
|
||||
The `OlmMachine` state machine works in a push/pull manner:
|
||||
|
||||
* You push state changes and events retrieved from a Matrix homeserver
|
||||
`/sync` response, into the state machine,
|
||||
|
||||
* You pull requests that you will need to send back to the homeserver
|
||||
out of the state machine.
|
||||
|
||||
- You push state changes and events retrieved from a Matrix homeserver
|
||||
`/sync` response, into the state machine,
|
||||
- You pull requests that you will need to send back to the homeserver
|
||||
out of the state machine.
|
||||
|
||||
```javascript
|
||||
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require('./index.js');
|
||||
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require("./index.js");
|
||||
|
||||
async function main() {
|
||||
// Define a user ID.
|
||||
const alice = new UserId('@alice:example.org');
|
||||
const alice = new UserId("@alice:example.org");
|
||||
|
||||
// Define a device ID.
|
||||
const device = new DeviceId('DEVICEID');
|
||||
const device = new DeviceId("DEVICEID");
|
||||
|
||||
// Let's create the `OlmMachine` state machine.
|
||||
const machine = await OlmMachine.initialize(alice, device);
|
||||
|
||||
// Let's pretend we have received changes and events from a
|
||||
// `/sync` endpoint of a Matrix homeserver, …
|
||||
const toDeviceEvents = "{}"; // JSON-encoded
|
||||
const toDeviceEvents = "[]"; // JSON-encoded list of events
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = {};
|
||||
const unusedFallbackKeys = [];
|
||||
@@ -198,8 +198,6 @@ $ npm run doc
|
||||
|
||||
The documentation is generated in the `./docs` directory.
|
||||
|
||||
|
||||
|
||||
[Node.js]: https://nodejs.org/
|
||||
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
|
||||
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Matrix SDK Crypto Node.js Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | filter(attribute="scope", value="crypto-nodejs") | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.id | truncate(length=7, end="") }}{% if commit.breaking %} [**breaking**] {% endif %}: {{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^refactor", group = "Refactoring"},
|
||||
{ message = "^ci", group = "Continuous Integration"},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = "v0.1.0-beta.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags chronologically
|
||||
date_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
@@ -1,18 +1,18 @@
|
||||
const { DownloaderHelper } = require('node-downloader-helper');
|
||||
const { HttpsProxyAgent } = require("https-proxy-agent");
|
||||
const { DownloaderHelper } = require("node-downloader-helper");
|
||||
const { version } = require("./package.json");
|
||||
const { platform, arch } = process
|
||||
const { platform, arch } = process;
|
||||
|
||||
const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download";
|
||||
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`;
|
||||
|
||||
const byteHelper = function (value) {
|
||||
if (value === 0) {
|
||||
return '0 b';
|
||||
return "0 b";
|
||||
}
|
||||
const units = ['b', 'kB', 'MB', 'GB', 'TB'];
|
||||
const units = ["b", "kB", "MB", "GB", "TB"];
|
||||
const number = Math.floor(Math.log(value) / Math.log(1024));
|
||||
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + ' ' +
|
||||
units[number];
|
||||
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + " " + units[number];
|
||||
};
|
||||
|
||||
function download_lib(libname) {
|
||||
@@ -24,9 +24,17 @@ function download_lib(libname) {
|
||||
override: true,
|
||||
});
|
||||
|
||||
dl.on('end', () => console.info('Download Completed'));
|
||||
dl.on('error', (err) => console.info('Download Failed', err));
|
||||
dl.on('progress', stats => {
|
||||
const proxy = process.env.https_proxy ?? process.env.HTTPS_PROXY;
|
||||
if (proxy) {
|
||||
const proxyAgent = new HttpsProxyAgent(proxy);
|
||||
dl.updateOptions({
|
||||
httpsRequestOptions: { agent: proxyAgent },
|
||||
});
|
||||
}
|
||||
|
||||
dl.on("end", () => console.info("Download Completed"));
|
||||
dl.on("error", (err) => console.info("Download Failed", err));
|
||||
dl.on("progress", (stats) => {
|
||||
const progress = stats.progress.toFixed(1);
|
||||
const speed = byteHelper(stats.speed);
|
||||
const downloaded = byteHelper(stats.downloaded);
|
||||
@@ -40,74 +48,74 @@ function download_lib(libname) {
|
||||
console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
|
||||
}
|
||||
});
|
||||
dl.start().catch(err => console.error(err));
|
||||
dl.start().catch((err) => console.error(err));
|
||||
}
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== "function") {
|
||||
try {
|
||||
return readFileSync("/usr/bin/ldd", "utf8").includes("musl");
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header;
|
||||
return !glibcVersionRuntime;
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.win32-x64-msvc.node')
|
||||
break
|
||||
case 'ia32':
|
||||
download_lib('matrix-sdk-crypto.win32-ia32-msvc.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.win32-arm64-msvc.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.darwin-x64.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.darwin-arm64.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-musl.node')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-gnu.node')
|
||||
case "win32":
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
download_lib("matrix-sdk-crypto.win32-x64-msvc.node");
|
||||
break;
|
||||
case "ia32":
|
||||
download_lib("matrix-sdk-crypto.win32-ia32-msvc.node");
|
||||
break;
|
||||
case "arm64":
|
||||
download_lib("matrix-sdk-crypto.win32-arm64-msvc.node");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`);
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
throw new Error('Linux for arm64 musl isn\'t support at the moment')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-arm64-gnu.node')
|
||||
break;
|
||||
case "darwin":
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
download_lib("matrix-sdk-crypto.darwin-x64.node");
|
||||
break;
|
||||
case "arm64":
|
||||
download_lib("matrix-sdk-crypto.darwin-arm64.node");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`);
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
download_lib('matrix-sdk-crypto.linux-arm-gnueabihf.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
break;
|
||||
case "linux":
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
if (isMusl()) {
|
||||
download_lib("matrix-sdk-crypto.linux-x64-musl.node");
|
||||
} else {
|
||||
download_lib("matrix-sdk-crypto.linux-x64-gnu.node");
|
||||
}
|
||||
break;
|
||||
case "arm64":
|
||||
if (isMusl()) {
|
||||
throw new Error("Linux for arm64 musl isn't support at the moment");
|
||||
} else {
|
||||
download_lib("matrix-sdk-crypto.linux-arm64-gnu.node");
|
||||
}
|
||||
break;
|
||||
case "arm":
|
||||
download_lib("matrix-sdk-crypto.linux-arm-gnueabihf.node");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
|
||||
"version": "0.1.0-beta.1",
|
||||
"version": "0.1.0-beta.4",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
@@ -15,6 +15,7 @@
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.9.0",
|
||||
"jest": "^28.1.0",
|
||||
"prettier": "^2.8.3",
|
||||
"typedoc": "^0.22.17",
|
||||
"yargs-parser": "~21.0.1"
|
||||
},
|
||||
@@ -22,6 +23,7 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "prettier --check .",
|
||||
"release-build": "napi build --platform --release --strip",
|
||||
"build": "napi build --platform",
|
||||
"postinstall": "node download-lib.js",
|
||||
@@ -29,6 +31,7 @@
|
||||
"doc": "typedoc --tsconfig ."
|
||||
},
|
||||
"dependencies": {
|
||||
"node-downloader-helper": "^2.1.1"
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"node-downloader-helper": "^2.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +50,11 @@ impl Attachment {
|
||||
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
|
||||
#[napi]
|
||||
pub fn decrypt(attachment: &mut EncryptedAttachment) -> napi::Result<Uint8Array> {
|
||||
let media_encryption_info = match attachment.media_encryption_info.take() {
|
||||
Some(media_encryption_info) => media_encryption_info,
|
||||
None => {
|
||||
return Err(napi::Error::from_reason(
|
||||
"The media encryption info are absent from the given encrypted attachment"
|
||||
.to_owned(),
|
||||
))
|
||||
}
|
||||
let Some(media_encryption_info) = attachment.media_encryption_info.take() else {
|
||||
return Err(napi::Error::from_reason(
|
||||
"The media encryption info are absent from the given encrypted attachment"
|
||||
.to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
let encrypted_data: &[u8] = attachment.encrypted_data.deref();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
|
||||
use napi::bindgen_prelude::{BigInt, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
|
||||
@@ -107,27 +108,33 @@ impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/// The verification state of the device that sent an event to us.
|
||||
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
|
||||
/// for more info.
|
||||
#[napi]
|
||||
pub enum VerificationState {
|
||||
/// The device is trusted.
|
||||
Trusted,
|
||||
|
||||
/// The device is not trusted.
|
||||
Untrusted,
|
||||
|
||||
/// The device is not known to us.
|
||||
UnknownDevice,
|
||||
pub enum ShieldColor {
|
||||
Red,
|
||||
Grey,
|
||||
None,
|
||||
}
|
||||
|
||||
impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState {
|
||||
fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self {
|
||||
use matrix_sdk_common::deserialized_responses::VerificationState::*;
|
||||
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
|
||||
/// for more info.
|
||||
#[napi]
|
||||
pub struct ShieldState {
|
||||
pub color: ShieldColor,
|
||||
pub message: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl From<RustShieldState> for ShieldState {
|
||||
fn from(value: RustShieldState) -> Self {
|
||||
match value {
|
||||
Trusted => Self::Trusted,
|
||||
Untrusted => Self::Untrusted,
|
||||
UnknownDevice => Self::UnknownDevice,
|
||||
RustShieldState::Red { message } => {
|
||||
ShieldState { color: ShieldColor::Red, message: Some(message) }
|
||||
}
|
||||
RustShieldState::Grey { message } => {
|
||||
ShieldState { color: ShieldColor::Grey, message: Some(message) }
|
||||
}
|
||||
RustShieldState::None => ShieldState { color: ShieldColor::None, message: None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
//#![warn(missing_docs, missing_debug_implementations)]
|
||||
|
||||
use napi_derive::napi;
|
||||
|
||||
pub mod attachment;
|
||||
pub mod encryption;
|
||||
mod errors;
|
||||
@@ -31,4 +33,25 @@ pub mod tracing;
|
||||
pub mod types;
|
||||
pub mod vodozemac;
|
||||
|
||||
/// Object containing the versions of the Rust libraries we are using.
|
||||
#[napi]
|
||||
pub struct Versions {
|
||||
/// The version of the vodozemac crate.
|
||||
#[napi(getter)]
|
||||
pub vodozemac: String,
|
||||
|
||||
/// The version of the matrix-sdk-crypto crate.
|
||||
#[napi(getter)]
|
||||
pub matrix_sdk_crypto: String,
|
||||
}
|
||||
|
||||
/// Get the versions of the Rust libraries we are using.
|
||||
#[napi(js_name = "getVersions")]
|
||||
pub fn get_versions() -> Versions {
|
||||
Versions {
|
||||
vodozemac: matrix_sdk_crypto::vodozemac::VERSION.to_owned(),
|
||||
matrix_sdk_crypto: matrix_sdk_crypto::VERSION.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
use crate::errors::into_err;
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
mem::ManuallyDrop,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use napi::bindgen_prelude::Either7;
|
||||
use napi::bindgen_prelude::{within_runtime_if_available, Either7, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
|
||||
use serde_json::{value::RawValue, Value as JsonValue};
|
||||
@@ -16,11 +18,61 @@ use crate::{
|
||||
sync_events, types, vodozemac,
|
||||
};
|
||||
|
||||
/// The value used by the `OlmMachine` JS class.
|
||||
///
|
||||
/// It has 2 states: `Opened` and `Closed`. Why maintaining the state here?
|
||||
/// Because NodeJS has no way to drop an object explicitly, and we want to be
|
||||
/// able to “close” the `OlmMachine` to free all associated data. More over,
|
||||
/// `napi-rs` doesn't allow a function to take the ownership of the type itself
|
||||
/// (`fn close(self) { … }`). So we manage the state ourselves.
|
||||
///
|
||||
/// Using the `OlmMachine` when its state is `Closed` will panic.
|
||||
enum OlmMachineInner {
|
||||
Opened(ManuallyDrop<matrix_sdk_crypto::OlmMachine>),
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl Drop for OlmMachineInner {
|
||||
fn drop(&mut self) {
|
||||
if let Self::Opened(machine) = self {
|
||||
// SAFETY: `self` won't be used anymore after this `take`, so it's safe to do it
|
||||
// here.
|
||||
let machine = unsafe { ManuallyDrop::take(machine) };
|
||||
within_runtime_if_available(move || drop(machine));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for OlmMachineInner {
|
||||
type Target = matrix_sdk_crypto::OlmMachine;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Opened(machine) => machine,
|
||||
Self::Closed => panic!("The `OlmMachine` has been closed, cannot use it anymore"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the type of store an `OlmMachine` can use.
|
||||
#[derive(Default)]
|
||||
#[napi]
|
||||
pub enum StoreType {
|
||||
/// Use `matrix-sdk-sled`.
|
||||
#[default]
|
||||
Sled,
|
||||
|
||||
/// Use `matrix-sdk-sqlite`.
|
||||
Sqlite,
|
||||
}
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
/// used for Matrix end to end encryption.
|
||||
// #[napi(custom_finalize)]
|
||||
#[napi]
|
||||
pub struct OlmMachine {
|
||||
inner: matrix_sdk_crypto::OlmMachine,
|
||||
inner: OlmMachineInner,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -36,7 +88,7 @@ impl OlmMachine {
|
||||
// the factory function. We also manually implement the
|
||||
// constructor to raise an error when called.
|
||||
|
||||
/// Create a new memory-based `OlmMachine` asynchronously.
|
||||
/// Create a new `OlmMachine` asynchronously.
|
||||
///
|
||||
/// The persistence of the encryption keys and all the inner
|
||||
/// objects are controlled by the `store_path` argument.
|
||||
@@ -59,40 +111,56 @@ impl OlmMachine {
|
||||
device_id: &identifiers::DeviceId,
|
||||
store_path: Option<String>,
|
||||
mut store_passphrase: Option<String>,
|
||||
store_type: Option<StoreType>,
|
||||
) -> napi::Result<OlmMachine> {
|
||||
let user_id = user_id.clone();
|
||||
let device_id = device_id.clone();
|
||||
let user_id = user_id.clone().inner;
|
||||
let device_id = device_id.clone().inner;
|
||||
|
||||
let store = store_path
|
||||
.map(|store_path| {
|
||||
matrix_sdk_sled::SledCryptoStore::open_with_passphrase(
|
||||
store_path,
|
||||
store_passphrase.as_deref(),
|
||||
)
|
||||
.map(Arc::new)
|
||||
.map_err(into_err)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
store_passphrase.zeroize();
|
||||
let user_id = user_id.as_ref();
|
||||
let device_id = device_id.as_ref();
|
||||
|
||||
Ok(OlmMachine {
|
||||
inner: match store {
|
||||
Some(store) => matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id.inner.as_ref(),
|
||||
device_id.inner.as_ref(),
|
||||
store,
|
||||
)
|
||||
.await
|
||||
.map_err(into_err)?,
|
||||
None => {
|
||||
matrix_sdk_crypto::OlmMachine::new(
|
||||
user_id.inner.as_ref(),
|
||||
device_id.inner.as_ref(),
|
||||
)
|
||||
.await
|
||||
inner: OlmMachineInner::Opened(ManuallyDrop::new(match store_path {
|
||||
Some(store_path) => {
|
||||
let machine = match store_type.unwrap_or_default() {
|
||||
StoreType::Sled => {
|
||||
matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id,
|
||||
device_id,
|
||||
matrix_sdk_sled::SledCryptoStore::open(
|
||||
store_path,
|
||||
store_passphrase.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map(Arc::new)
|
||||
.map_err(into_err)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
StoreType::Sqlite => {
|
||||
matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id,
|
||||
device_id,
|
||||
matrix_sdk_sqlite::SqliteCryptoStore::open(
|
||||
store_path,
|
||||
store_passphrase.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map(Arc::new)
|
||||
.map_err(into_err)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
||||
store_passphrase.zeroize();
|
||||
|
||||
machine.map_err(into_err)?
|
||||
}
|
||||
},
|
||||
|
||||
None => matrix_sdk_crypto::OlmMachine::new(user_id, device_id).await,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -308,10 +376,12 @@ impl OlmMachine {
|
||||
///
|
||||
/// * `users`, an array over user IDs that should be marked for tracking.
|
||||
#[napi(strict)]
|
||||
pub async fn update_tracked_users(&self, users: Vec<&identifiers::UserId>) {
|
||||
pub async fn update_tracked_users(&self, users: Vec<&identifiers::UserId>) -> napi::Result<()> {
|
||||
let users = users.into_iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
|
||||
|
||||
self.inner.update_tracked_users(users.iter().map(AsRef::as_ref)).await;
|
||||
self.inner.update_tracked_users(users.iter().map(AsRef::as_ref)).await.map_err(into_err)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get to-device requests to share a room key with users in a room.
|
||||
@@ -407,4 +477,21 @@ impl OlmMachine {
|
||||
pub async fn sign(&self, message: String) -> types::Signatures {
|
||||
self.inner.sign(message.as_str()).await.into()
|
||||
}
|
||||
|
||||
/// Shut down the `OlmMachine`.
|
||||
///
|
||||
/// The `OlmMachine` cannot be used after this method has been called,
|
||||
/// otherwise it will panic.
|
||||
///
|
||||
/// All associated resources will be closed too, like the crypto storage
|
||||
/// connections.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller is responsible to **not** use any objects that came from this
|
||||
/// `OlmMachine` after this `close` method has been called.
|
||||
#[napi(strict)]
|
||||
pub fn close(&mut self) {
|
||||
self.inner = OlmMachineInner::Closed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,13 @@ use matrix_sdk_crypto::requests::{
|
||||
};
|
||||
use napi::bindgen_prelude::{Either7, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
use ruma::api::client::keys::{
|
||||
claim_keys::v3::Request as RumaKeysClaimRequest,
|
||||
upload_keys::v3::Request as RumaKeysUploadRequest,
|
||||
upload_signatures::v3::Request as RumaSignatureUploadRequest,
|
||||
use ruma::{
|
||||
api::client::keys::{
|
||||
claim_keys::v3::Request as RumaKeysClaimRequest,
|
||||
upload_keys::v3::Request as RumaKeysUploadRequest,
|
||||
upload_signatures::v3::Request as RumaSignatureUploadRequest,
|
||||
},
|
||||
events::EventContent,
|
||||
};
|
||||
|
||||
use crate::into_err;
|
||||
@@ -28,11 +31,10 @@ pub struct KeysUploadRequest {
|
||||
#[napi(readonly)]
|
||||
pub id: String,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A JSON-encoded string containing the rest of the payload: `device_keys`,
|
||||
/// `one_time_keys`, `fallback_keys`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"device_keys": …, "one_time_keys": …, "fallback_keys": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[napi(readonly)]
|
||||
pub body: String,
|
||||
}
|
||||
@@ -61,7 +63,7 @@ pub struct KeysQueryRequest {
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"timeout": …, "device_keys": …, "token": …}
|
||||
/// {"timeout": …, "one_time_keys": …}
|
||||
/// ```
|
||||
#[napi(readonly)]
|
||||
pub body: String,
|
||||
@@ -92,7 +94,7 @@ pub struct KeysClaimRequest {
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"timeout": …, "one_time_keys": …}
|
||||
/// {"event_type": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
#[napi(readonly)]
|
||||
pub body: String,
|
||||
@@ -119,11 +121,18 @@ pub struct ToDeviceRequest {
|
||||
#[napi(readonly)]
|
||||
pub id: String,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A string representing the type of event being sent to each devices.
|
||||
#[napi(readonly)]
|
||||
pub event_type: String,
|
||||
|
||||
/// A string representing a request identifier unique to the access token
|
||||
/// used to send the request.
|
||||
#[napi(readonly)]
|
||||
pub txn_id: String,
|
||||
|
||||
/// A JSON-encoded string containing the rest of the payload: `messages`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"event_type": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[napi(readonly)]
|
||||
pub body: String,
|
||||
}
|
||||
@@ -149,11 +158,9 @@ pub struct SignatureUploadRequest {
|
||||
#[napi(readonly)]
|
||||
pub id: String,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A JSON-encoded string containing the rest of the payload: `signed_keys`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"signed_keys": …, "txn_id": …, "messages": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[napi(readonly)]
|
||||
pub body: String,
|
||||
}
|
||||
@@ -177,13 +184,25 @@ pub struct RoomMessageRequest {
|
||||
#[napi(readonly)]
|
||||
pub id: String,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"room_id": …, "txn_id": …, "content": …}
|
||||
/// ```
|
||||
/// A string representing the room to send the event to.
|
||||
#[napi(readonly)]
|
||||
pub body: String,
|
||||
pub room_id: String,
|
||||
|
||||
/// A string representing the transaction ID for this event.
|
||||
///
|
||||
/// Clients should generate an ID unique across requests with the same
|
||||
/// access token; it will be used by the server to ensure idempotency of
|
||||
/// requests.
|
||||
#[napi(readonly)]
|
||||
pub txn_id: String,
|
||||
|
||||
/// A string representing the type of event to be sent.
|
||||
#[napi(readonly)]
|
||||
pub event_type: String,
|
||||
|
||||
/// A JSON-encoded string containing the message's content.
|
||||
#[napi(readonly, js_name = "body")]
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -205,11 +224,9 @@ pub struct KeysBackupRequest {
|
||||
#[napi(readonly)]
|
||||
pub id: String,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
/// A JSON-encoded string containing the rest of the payload: `rooms`.
|
||||
///
|
||||
/// ```json
|
||||
/// {"rooms": …}
|
||||
/// ```
|
||||
/// It represents the body of the HTTP request.
|
||||
#[napi(readonly)]
|
||||
pub body: String,
|
||||
}
|
||||
@@ -224,43 +241,89 @@ impl KeysBackupRequest {
|
||||
}
|
||||
|
||||
macro_rules! request {
|
||||
($request:ident from $ruma_request:ident maps fields $( $field:ident $( { $transformation:expr } )? ),+ $(,)? ) => {
|
||||
impl TryFrom<(String, &$ruma_request)> for $request {
|
||||
(
|
||||
$destination_request:ident from $source_request:ident
|
||||
$( extracts $( $field_name:ident : $field_type:tt ),+ $(,)? )?
|
||||
$( $( and )? groups $( $grouped_field_name:ident $( { $grouped_field_transformation:expr } )? ),+ $(,)? )?
|
||||
) => {
|
||||
impl TryFrom<(String, &$source_request)> for $destination_request {
|
||||
type Error = napi::Error;
|
||||
|
||||
fn try_from(
|
||||
(request_id, request): (String, &$ruma_request),
|
||||
(request_id, request): (String, &$source_request),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let mut map = serde_json::Map::new();
|
||||
$(
|
||||
let field = &request.$field;
|
||||
$(
|
||||
let field = {
|
||||
let $field = field;
|
||||
|
||||
$transformation
|
||||
};
|
||||
)?
|
||||
map.insert(stringify!($field).to_owned(), serde_json::to_value(field).map_err(into_err)?);
|
||||
)+
|
||||
let value = serde_json::Value::Object(map);
|
||||
|
||||
Ok($request {
|
||||
id: request_id,
|
||||
body: serde_json::to_string(&value).map_err(into_err)?.into(),
|
||||
})
|
||||
request!(
|
||||
@__try_from $destination_request from $source_request
|
||||
(request_id = request_id.into(), request = request)
|
||||
$( extracts [ $( $field_name : $field_type, )+ ] )?
|
||||
$( groups [ $( $grouped_field_name $( { $grouped_field_transformation } )? , )+ ] )?
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
@__try_from $destination_request:ident from $source_request:ident
|
||||
(request_id = $request_id:expr, request = $request:expr)
|
||||
$( extracts [ $( $field_name:ident : $field_type:tt ),* $(,)? ] )?
|
||||
$( groups [ $( $grouped_field_name:ident $( { $grouped_field_transformation:expr } )? ),* $(,)? ] )?
|
||||
) => {
|
||||
{
|
||||
Ok($destination_request {
|
||||
id: $request_id,
|
||||
$(
|
||||
$(
|
||||
$field_name: request!(@__field $field_name : $field_type ; request = $request),
|
||||
)*
|
||||
)?
|
||||
$(
|
||||
body: {
|
||||
let mut map = serde_json::Map::new();
|
||||
$(
|
||||
|
||||
let field = &$request.$grouped_field_name;
|
||||
$(
|
||||
let field = {
|
||||
let $grouped_field_name = field;
|
||||
|
||||
$grouped_field_transformation
|
||||
};
|
||||
)?
|
||||
map.insert(stringify!($grouped_field_name).to_owned(), serde_json::to_value(field).map_err(into_err)?);
|
||||
)*
|
||||
let object = serde_json::Value::Object(map);
|
||||
|
||||
serde_json::to_string(&object).map_err(into_err)?.into()
|
||||
}
|
||||
)?
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
( @__field $field_name:ident : $field_type:ident ; request = $request:expr ) => {
|
||||
request!(@__field_type as $field_type ; request = $request, field_name = $field_name)
|
||||
};
|
||||
|
||||
( @__field_type as string ; request = $request:expr, field_name = $field_name:ident ) => {
|
||||
$request.$field_name.to_string().into()
|
||||
};
|
||||
|
||||
( @__field_type as json ; request = $request:expr, field_name = $field_name:ident ) => {
|
||||
serde_json::to_string(&$request.$field_name).map_err(into_err)?.into()
|
||||
};
|
||||
|
||||
( @__field_type as event_type ; request = $request:expr, field_name = $field_name:ident ) => {
|
||||
$request.content.event_type().to_string().into()
|
||||
};
|
||||
}
|
||||
|
||||
request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, device_keys, token);
|
||||
request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, one_time_keys);
|
||||
request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages);
|
||||
request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys);
|
||||
request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content);
|
||||
request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms);
|
||||
request!(KeysUploadRequest from RumaKeysUploadRequest groups device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from RumaKeysQueryRequest groups timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, device_keys, token);
|
||||
request!(KeysClaimRequest from RumaKeysClaimRequest groups timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, one_time_keys);
|
||||
request!(ToDeviceRequest from RumaToDeviceRequest extracts event_type: string, txn_id: string and groups messages);
|
||||
request!(SignatureUploadRequest from RumaSignatureUploadRequest groups signed_keys);
|
||||
request!(RoomMessageRequest from RumaRoomMessageRequest extracts room_id: string, txn_id: string, event_type: event_type, content: json);
|
||||
request!(KeysBackupRequest from RumaKeysBackupRequest groups rooms);
|
||||
|
||||
pub type OutgoingRequests = Either7<
|
||||
KeysUploadRequest,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
|
||||
use matrix_sdk_crypto::IncomingResponse;
|
||||
use napi_derive::*;
|
||||
@@ -152,7 +150,7 @@ impl DecryptedRoomEvent {
|
||||
/// trusted.
|
||||
#[napi(getter)]
|
||||
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
|
||||
Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone()))
|
||||
Some(self.encryption_info.as_ref()?.sender_device.as_ref()?.clone().into())
|
||||
}
|
||||
|
||||
/// The Curve25519 key of the device that created the megolm
|
||||
@@ -186,9 +184,14 @@ impl DecryptedRoomEvent {
|
||||
/// note this is the state of the device at the time of
|
||||
/// decryption. It may change in the future if a device gets
|
||||
/// verified or deleted.
|
||||
#[napi(getter)]
|
||||
pub fn verification_state(&self) -> Option<encryption::VerificationState> {
|
||||
Some(self.encryption_info.as_ref()?.verification_state.borrow().into())
|
||||
#[napi]
|
||||
pub fn shield_state(&self, strict: bool) -> Option<encryption::ShieldState> {
|
||||
let state = &self.encryption_info.as_ref()?.verification_state;
|
||||
if strict {
|
||||
Some(state.to_shield_state_strict().into())
|
||||
} else {
|
||||
Some(state.to_shield_state_lax().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::identifiers;
|
||||
/// Information on E2E device updates.
|
||||
#[napi]
|
||||
pub struct DeviceLists {
|
||||
pub(crate) inner: ruma::api::client::sync::sync_events::v3::DeviceLists,
|
||||
pub(crate) inner: ruma::api::client::sync::sync_events::DeviceLists,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -18,7 +18,7 @@ impl DeviceLists {
|
||||
changed: Option<Vec<&identifiers::UserId>>,
|
||||
left: Option<Vec<&identifiers::UserId>>,
|
||||
) -> Self {
|
||||
let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default();
|
||||
let mut inner = ruma::api::client::sync::sync_events::DeviceLists::default();
|
||||
|
||||
inner.changed = changed.into_iter().flatten().map(|user| user.inner.clone()).collect();
|
||||
inner.left = left.into_iter().flatten().map(|user| user.inner.clone()).collect();
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
const { Attachment, EncryptedAttachment } = require('../');
|
||||
const { Attachment, EncryptedAttachment } = require("../");
|
||||
|
||||
describe(Attachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const originalData = "hello";
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
let encryptedAttachment;
|
||||
|
||||
test('can encrypt data', () => {
|
||||
|
||||
test("can encrypt data", () => {
|
||||
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
|
||||
|
||||
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
|
||||
|
||||
expect(mediaEncryptionInfo).toMatchObject({
|
||||
v: 'v2',
|
||||
v: "v2",
|
||||
key: {
|
||||
kty: expect.any(String),
|
||||
key_ops: expect.arrayContaining(['encrypt', 'decrypt']),
|
||||
key_ops: expect.arrayContaining(["encrypt", "decrypt"]),
|
||||
alg: expect.any(String),
|
||||
k: expect.any(String),
|
||||
ext: expect.any(Boolean),
|
||||
},
|
||||
iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
hashes: {
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/)
|
||||
}
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
},
|
||||
});
|
||||
|
||||
const encryptedData = encryptedAttachment.encryptedData;
|
||||
expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false);
|
||||
expect(
|
||||
encryptedData.every((i) => {
|
||||
i != 0;
|
||||
}),
|
||||
).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can decrypt data', () => {
|
||||
test("can decrypt data", () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
|
||||
@@ -40,34 +44,36 @@ describe(Attachment.name, () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can only decrypt once', () => {
|
||||
test("can only decrypt once", () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
|
||||
expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow()
|
||||
expect(() => {
|
||||
textDecoder.decode(decryptedAttachment);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptedAttachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const originalData = "hello";
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
test('can be created manually', () => {
|
||||
test("can be created manually", () => {
|
||||
const encryptedAttachment = new EncryptedAttachment(
|
||||
new Uint8Array([24, 150, 67, 37, 144]),
|
||||
JSON.stringify({
|
||||
v: 'v2',
|
||||
v: "v2",
|
||||
key: {
|
||||
kty: 'oct',
|
||||
key_ops: [ 'encrypt', 'decrypt' ],
|
||||
alg: 'A256CTR',
|
||||
k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM',
|
||||
ext: true
|
||||
kty: "oct",
|
||||
key_ops: ["encrypt", "decrypt"],
|
||||
alg: "A256CTR",
|
||||
k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM",
|
||||
ext: true,
|
||||
},
|
||||
iv: 'xk2AcWkomiYAAAAAAAAAAA',
|
||||
iv: "xk2AcWkomiYAAAAAAAAAAA",
|
||||
hashes: {
|
||||
sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok'
|
||||
}
|
||||
})
|
||||
sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../');
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require("../");
|
||||
|
||||
describe('EncryptionAlgorithm', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("EncryptionAlgorithm", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
|
||||
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptionSettings.name, () => {
|
||||
test('can be instantiated with default values', () => {
|
||||
test("can be instantiated with default values", () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
|
||||
@@ -17,20 +17,14 @@ describe(EncryptionSettings.name, () => {
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
|
||||
});
|
||||
|
||||
test('checks the history visibility values', () => {
|
||||
test("checks the history visibility values", () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
es.historyVisibility = HistoryVisibility.Invited;
|
||||
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
|
||||
expect(() => { es.historyVisibility = 42 }).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationState', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(VerificationState.Trusted).toStrictEqual(0);
|
||||
expect(VerificationState.Untrusted).toStrictEqual(1);
|
||||
expect(VerificationState.UnknownDevice).toStrictEqual(2);
|
||||
expect(() => {
|
||||
es.historyVisibility = 42;
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { HistoryVisibility } = require('../');
|
||||
const { HistoryVisibility } = require("../");
|
||||
|
||||
describe('HistoryVisibility', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("HistoryVisibility", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(HistoryVisibility.Invited).toStrictEqual(0);
|
||||
expect(HistoryVisibility.Joined).toStrictEqual(1);
|
||||
expect(HistoryVisibility.Shared).toStrictEqual(2);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user