Compare commits
1136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6aec202938 | |||
| 4e4187ad57 | |||
| c1e921db6a | |||
| 88eb1fce25 | |||
| 65eb043c0b | |||
| 5e78515216 | |||
| 4c6181c09d | |||
| 8cdc55b266 | |||
| 0c340a2cda | |||
| 496f7244ce | |||
| 2924551bff | |||
| beacd21b46 | |||
| 9d3500c0c5 | |||
| 860a69b246 | |||
| aeb1a4b22e | |||
| dce22ba37c | |||
| 5ef2a22cc7 | |||
| 53cef63bdb | |||
| bfeb787e6f | |||
| 1f749df59d | |||
| c063cf83a6 | |||
| e83c4d3bea | |||
| 54114f03b5 | |||
| 8745fb4230 | |||
| a38a9f604d | |||
| 7f99c2828b | |||
| 2630629cdb | |||
| 106267eb1a | |||
| c8168adeb2 | |||
| 3773800133 | |||
| ff2e24afbc | |||
| f47b7cfb13 | |||
| e2b9a9468a | |||
| 981f4de397 | |||
| a3192a190f | |||
| 37f5bfdf02 | |||
| ccc516903c | |||
| 97a944a33e | |||
| f9d3ee7fc4 | |||
| c3e18cedd5 | |||
| fe17bdaf49 | |||
| 8249475fba | |||
| 65b9808662 | |||
| fa1bcabe5a | |||
| 20b25b1074 | |||
| e6ec803818 | |||
| 57c43a1f6b | |||
| e7b8ab8c10 | |||
| abbf7041a4 | |||
| 903ef44328 | |||
| 44c48ef8ee | |||
| a77881d284 | |||
| 8baa236d81 | |||
| 4af5788bba | |||
| c5dd6bf93d | |||
| fda308694e | |||
| 8f4dff0074 | |||
| b8edb5c436 | |||
| 0bf74a0b36 | |||
| 1200a2f02d | |||
| 9ab2517b87 | |||
| 2b48e85554 | |||
| 00d31267fa | |||
| f8b2900698 | |||
| bf2ff5cd78 | |||
| d3bb0b216d | |||
| 526d7fa27d | |||
| befad123f6 | |||
| 7d49ac8d25 | |||
| d628e065f1 | |||
| 1412fdb6a7 | |||
| 6654d72e78 | |||
| d7150cfa58 | |||
| 99e818952f | |||
| a717a8aabf | |||
| 10520469dd | |||
| ffd941169e | |||
| fd874845cd | |||
| d7e4374054 | |||
| 044b12cdf1 | |||
| 060d0b4f02 | |||
| 4b2a6f20f3 | |||
| 1eb211dbd4 | |||
| 40e67e7809 | |||
| 146bbef3af | |||
| ab61ef8680 | |||
| dbd657646c | |||
| beffc45cbe | |||
| 96bbf9c3f5 | |||
| 85680b22d9 | |||
| e226001530 | |||
| 2a6dd1d5a8 | |||
| 62e61328a1 | |||
| d8776d35ca | |||
| dfefd2383e | |||
| 53f29fd4a5 | |||
| 3cbf1fa754 | |||
| a2e9590420 | |||
| 9dc9ccd9d4 | |||
| b662edc343 | |||
| 279e46e045 | |||
| 4fd07aef10 | |||
| d3227ab157 | |||
| 353c8287db | |||
| 16f466b795 | |||
| 65db7ef25d | |||
| 7088fb3ffc | |||
| 53c1aaa256 | |||
| 306bbb1360 | |||
| c2e3582032 | |||
| dddac13496 | |||
| f07966c3fc | |||
| b468be83da | |||
| 25cfa26c56 | |||
| 3c8aad49fc | |||
| fd68994e64 | |||
| 61e0ec5ab5 | |||
| 0d33d35437 | |||
| 213fd12ff7 | |||
| 2d323f180f | |||
| 3f06ccc36b | |||
| cb82299f0e | |||
| ad52e06546 | |||
| f5a3d7eea3 | |||
| d47f0be2f2 | |||
| 7cb78b2090 | |||
| c4bc6d2799 | |||
| 3b6110a42e | |||
| 7e1c957090 | |||
| f03101cc0b | |||
| 6766cf3470 | |||
| b564cd935e | |||
| d859a98367 | |||
| d02e248328 | |||
| c401222a84 | |||
| 425746e215 | |||
| aee3286793 | |||
| 941bd6d846 | |||
| 7228806eca | |||
| 74e641b57c | |||
| 37fd895d13 | |||
| ef062e15ac | |||
| b9143ebc9e | |||
| 7083294b21 | |||
| 9e068ef063 | |||
| 1e99e6dc07 | |||
| d576a17d33 | |||
| 40a14a10b1 | |||
| 8a51503b34 | |||
| 5109ef290c | |||
| 6e3b2b54df | |||
| 0048f1a805 | |||
| 5caf093dca | |||
| 478084a5f1 | |||
| db02e828a6 | |||
| 3a4bdc861a | |||
| eae2fe675d | |||
| df677362ed | |||
| 776a857779 | |||
| 3ccaf77249 | |||
| 6254169151 | |||
| 684f7a1432 | |||
| e6ac966757 | |||
| bb235cb7f1 | |||
| 1b844292a9 | |||
| 2bd837a1e4 | |||
| fced1159ce | |||
| 4095f33fca | |||
| c4f15a3f97 | |||
| 8a149570a9 | |||
| 73f07233ff | |||
| 81ac2b97f4 | |||
| a44272be60 | |||
| 3b2461080c | |||
| 270d7567df | |||
| b2789bf2a1 | |||
| 6d1d8cc5dd | |||
| 560b01974f | |||
| c08ab00c57 | |||
| aaee1bf49a | |||
| 5af61d161a | |||
| ebd2c25d60 | |||
| c0044499da | |||
| d8e4c8d24d | |||
| b4805d27e2 | |||
| e76fc79a33 | |||
| f5e3e10707 | |||
| 2eb237cc0e | |||
| a7ac2e1ad7 | |||
| 5d2fb6bdfa | |||
| b08a4c0930 | |||
| 64bb7ea350 | |||
| 0546c561a9 | |||
| 72ed65dc5f | |||
| b8bd872cdf | |||
| 3ade837579 | |||
| ab5656ce94 | |||
| 78e1f916f2 | |||
| b99a52bc1b | |||
| 1271d6c1f5 | |||
| fe85fefb5b | |||
| 7143ab9b53 | |||
| 8279efb684 | |||
| f00bc5a3d1 | |||
| 51ae70c02b | |||
| dd466281dd | |||
| e8a5320ae6 | |||
| c5efc716c9 | |||
| 1b23b87abe | |||
| 486980b906 | |||
| 8160c5295b | |||
| c625c00d58 | |||
| 8226a6f0e8 | |||
| 58f59277cc | |||
| c6eb6cf552 | |||
| 858481e1d2 | |||
| a9a3e98801 | |||
| fdd243a012 | |||
| 30fcc54d8f | |||
| aa7898eca2 | |||
| b349b0e166 | |||
| 9b7b359351 | |||
| d84951b6a5 | |||
| 744a004d06 | |||
| 29f4ea25e8 | |||
| f0aa297054 | |||
| 1b35de4326 | |||
| 0caddb1610 | |||
| 3ccda81a24 | |||
| 817fc91a33 | |||
| 562aeefc7a | |||
| cf01679c5c | |||
| 17b7f69b0c | |||
| 4abb4d0b45 | |||
| 986f285f4c | |||
| 2053eff036 | |||
| 2b1781b06d | |||
| 1a344a4331 | |||
| 6c730c217f | |||
| d3594b620c | |||
| e194194478 | |||
| f73d6371d2 | |||
| 8b90953ae0 | |||
| 9eae7a23ce | |||
| 374da49bcd | |||
| b343410840 | |||
| 76cf73b70b | |||
| 9f416b6570 | |||
| 8cde30459b | |||
| 7ea3c68820 | |||
| 1d26ec1ada | |||
| 4992498942 | |||
| 3852c99f9e | |||
| ff4284a3b9 | |||
| c167aca3e3 | |||
| 937e15e631 | |||
| 59dcf77bf5 | |||
| a411dec540 | |||
| 20f2f1e04f | |||
| b6727ac7b4 | |||
| 8ed53847d3 | |||
| 0f5167b57f | |||
| 30611d0a68 | |||
| 3a2ad5ae10 | |||
| 63dfbc1465 | |||
| 1f69f09072 | |||
| 575bcaa43b | |||
| b85bc53002 | |||
| b8f0fdefee | |||
| 3851e9e350 | |||
| 73ea1e29e0 | |||
| a4c9bc1df6 | |||
| ec93fdbbde | |||
| 1d36f73e89 | |||
| da9593cee2 | |||
| b6e2c25c09 | |||
| 03aac5bd35 | |||
| 3cb813d056 | |||
| 6606ba7279 | |||
| e600dc7c4f | |||
| 88eef54cc5 | |||
| b922c869f0 | |||
| 0c90afeaa2 | |||
| 3756de9e86 | |||
| 7fd3e28f2a | |||
| 511ec78b2c | |||
| c71a1ae71d | |||
| 8c6744dd96 | |||
| b489d422a2 | |||
| 71c465afd5 | |||
| c6cceb8d4d | |||
| 8f48836fd4 | |||
| 594e42793e | |||
| dfb7c82c31 | |||
| 1418d068b0 | |||
| 8febd59588 | |||
| 0e6d0c5bda | |||
| f297ed7323 | |||
| 811e18b36e | |||
| d808c9dab4 | |||
| 2eec9dc8a1 | |||
| 3f3dfd404c | |||
| e03fd54739 | |||
| f136f8e75a | |||
| b27ebc3eed | |||
| 732bf7472e | |||
| 87bbe9c864 | |||
| d77f2b20ce | |||
| 04dec97109 | |||
| 9b81c2eb68 | |||
| f599502eab | |||
| a5c65b27d9 | |||
| bd86ef07a9 | |||
| 3f80a00bf1 | |||
| bfad2168b1 | |||
| d807bcd1ee | |||
| 9612141129 | |||
| 285e5dc5ec | |||
| 0928a5aaeb | |||
| f2eb52fbcf | |||
| aa93e611ee | |||
| b68689dc4b | |||
| 8229cb2c74 | |||
| bdf1e5bef9 | |||
| 1a8573632f | |||
| a62744a177 | |||
| a6c389b5a6 | |||
| f72b456cdd | |||
| 6515666b84 | |||
| 16c92bebdf | |||
| 15b626bb6f | |||
| ef4929dacc | |||
| 9391e7148a | |||
| 1f06aa2fb9 | |||
| 800c41ce97 | |||
| 1fa599dd63 | |||
| bf93739ef5 | |||
| a9e0a1d634 | |||
| ab24008c87 | |||
| 1f53dcc56f | |||
| 9f49746b44 | |||
| 7f999d5893 | |||
| 852d127a2a | |||
| 0d3084b956 | |||
| da8365ee14 | |||
| 95d1912e18 | |||
| ae27a5e4c3 | |||
| 28b5673cbf | |||
| 5aee54fc1d | |||
| 84cb98ca96 | |||
| 8ea9b14e94 | |||
| 83e22dbad9 | |||
| 3497ae1043 | |||
| 4e4024aba6 | |||
| 95160a2ba3 | |||
| 3fdf7b4f10 | |||
| c5ec0591e1 | |||
| 82bcb3f79f | |||
| 0ab7e4ddd9 | |||
| 2df3a262ef | |||
| 5f393de34b | |||
| 48c77bcb0d | |||
| 0084c74f97 | |||
| 7d9e2ef64a | |||
| 75df3a87b8 | |||
| 4c9c742795 | |||
| 30bf382d0a | |||
| 881ab4f28e | |||
| 851ccfd736 | |||
| 133d36b559 | |||
| d6824ccad2 | |||
| 273a61d7de | |||
| e8b9d641ba | |||
| 61b9fac25b | |||
| 0d5fe1a788 | |||
| bc8e5fd6f6 | |||
| 441e586915 | |||
| bed0badc15 | |||
| 448fc4a361 | |||
| fd33589326 | |||
| 1080ad5ac1 | |||
| 7723dc7aed | |||
| b2f41de27b | |||
| ed9ee27340 | |||
| e1cc16fe81 | |||
| 937becb1ac | |||
| fe93a3f4d5 | |||
| fee81aaff7 | |||
| b1207caf7c | |||
| 5fd8987eb9 | |||
| f858d407ca | |||
| 8caf218aed | |||
| 6c32d64194 | |||
| 7a99905f1d | |||
| 25a8f63912 | |||
| c6fa539845 | |||
| 4c900bbfff | |||
| 165d022015 | |||
| 495b0c7ed9 | |||
| a491f7543a | |||
| 901996345c | |||
| 15c0c731e9 | |||
| fad5c98629 | |||
| de2dbf7156 | |||
| 1e1e1db4f5 | |||
| c7c52e68b5 | |||
| ac5d16899e | |||
| 9e890da986 | |||
| ab2703e6c1 | |||
| 21877c240b | |||
| e091d30f6e | |||
| 04e415eb0c | |||
| 35f5816da8 | |||
| efda3ee834 | |||
| ce6d858b88 | |||
| 82232f970a | |||
| b72ce283b4 | |||
| cc5627f0c3 | |||
| 0f03221fb9 | |||
| 98ea9dccdf | |||
| 8747dc9d72 | |||
| a4d500f0f5 | |||
| 9e0735498b | |||
| 12fc906542 | |||
| 94479cdeec | |||
| bcbebe944d | |||
| 199377d466 | |||
| 98a19e7823 | |||
| d9a95917e3 | |||
| 6432e708d6 | |||
| 409bfb4f08 | |||
| c921223ac2 | |||
| 8c68a812e2 | |||
| 79b18a4789 | |||
| f3da6d4258 | |||
| 1e938dfd55 | |||
| e48b28eefd | |||
| ea5223ddf5 | |||
| 65772cdc1e | |||
| f8a468135a | |||
| 5a4bf6f08a | |||
| dbf3c0d6ea | |||
| abd937e308 | |||
| f23fd57e8d | |||
| e9f96e030f | |||
| 02add8d416 | |||
| a5cee1fa62 | |||
| bc18173ffa | |||
| 3ac46255f0 | |||
| b8530c91c1 | |||
| f13e5ae186 | |||
| 0780f6c501 | |||
| 09ac19fb45 | |||
| bb29421854 | |||
| 2ac707d57c | |||
| af5c389eea | |||
| b27b619412 | |||
| 7b0aee7a2c | |||
| 807e4234d6 | |||
| 51e43547ed | |||
| 2e63f95b76 | |||
| 32f067256b | |||
| e97102d3e4 | |||
| 851bad7864 | |||
| 728f5e237a | |||
| 9767dd58ca | |||
| f33e9e80f6 | |||
| 0ad648703f | |||
| a3b3cc1edd | |||
| 793d42c33a | |||
| 03f7be502b | |||
| 3409dfcdf3 | |||
| fd89dc36ec | |||
| 9797b8da74 | |||
| 6b879e88e7 | |||
| d3199778e4 | |||
| c426b09b07 | |||
| 3c6e6d1ad7 | |||
| 46055fe8c4 | |||
| d75ac203d2 | |||
| c4c004cd9c | |||
| c956576231 | |||
| 2d99b2b93a | |||
| 713a7a6fe8 | |||
| 00dc25a94f | |||
| a73585e13a | |||
| acc0f8430a | |||
| aa251c2450 | |||
| bb0737e0c8 | |||
| 536b84c55c | |||
| b427ffc80d | |||
| d729e05d98 | |||
| b93457b863 | |||
| f02b37f5c9 | |||
| 8c9d68041c | |||
| d9613043ec | |||
| 4c4ec08122 | |||
| 62f4c8bebd | |||
| 7a68499c11 | |||
| ef64871f78 | |||
| 81ed98d3ec | |||
| 657e092887 | |||
| 53a3ceaa00 | |||
| b9f3000dcc | |||
| 04db04ff62 | |||
| b06b842885 | |||
| 2b73aa9976 | |||
| e63012d979 | |||
| 17a513d5fb | |||
| ae7c1467ae | |||
| 80f92254e0 | |||
| 4ab9b8414d | |||
| 79f9372ebb | |||
| 482deb718c | |||
| 473c5cd43d | |||
| 07061c2ca7 | |||
| e23dd57213 | |||
| eb3aa0baf1 | |||
| d7b069e0e5 | |||
| 24d9825a49 | |||
| ca1d5b0803 | |||
| 00ab655848 | |||
| d21a4bd4de | |||
| 30da43060f | |||
| 922223219d | |||
| dee2541cc3 | |||
| a6cbdf5662 | |||
| d189e6f55d | |||
| 1d58d1c2f1 | |||
| 78e93cfa15 | |||
| 516077086c | |||
| b20555c622 | |||
| 742c428a25 | |||
| 58f839ecd8 | |||
| dd2e4f153a | |||
| 74bf2ed9f4 | |||
| 677791d64c | |||
| cdbadaf581 | |||
| ee8da3a019 | |||
| 6a33168973 | |||
| 8f5a27cb4a | |||
| 9d024f35a3 | |||
| 3e40d90b72 | |||
| 8731934f46 | |||
| ef8872896f | |||
| c9cefa9b66 | |||
| 1bc68ddfa1 | |||
| b8fed18c77 | |||
| 0785ad67f5 | |||
| 1a6226f2bf | |||
| b0e5f7c808 | |||
| a9ad990452 | |||
| 52aede3d99 | |||
| c662dad57f | |||
| 4ace90ad8b | |||
| 6168cb7276 | |||
| b112f82a7b | |||
| 3c45d17e71 | |||
| e5580df77b | |||
| a55b87809e | |||
| 1ab4e3efeb | |||
| b009eb0a4d | |||
| 416e1f19e0 | |||
| bdda6e2cf5 | |||
| c94691d68e | |||
| 83aa0a37b1 | |||
| a198268009 | |||
| e1b3a22bc5 | |||
| 398fa5c643 | |||
| 559cffdb56 | |||
| da6aac176b | |||
| d71d2effe6 | |||
| aa7ff75be7 | |||
| 80537045b4 | |||
| 8cf37e10ce | |||
| a1af0b9345 | |||
| ea2be1a7ec | |||
| d6ce495e08 | |||
| 0e85f6de0f | |||
| 1b787c94bf | |||
| a4ead85a01 | |||
| d744fa587f | |||
| 0fee2b0ceb | |||
| 28d8fbedc9 | |||
| 6fb991a1b0 | |||
| d41023d26f | |||
| 0093951507 | |||
| 3db34e7717 | |||
| 9913f6f041 | |||
| 6f83785b3a | |||
| ce56b525bb | |||
| 44e146e2a2 | |||
| ef20979753 | |||
| a187399c7d | |||
| f5bbfa8975 | |||
| 18be267b64 | |||
| 2d35ec354e | |||
| b7d8dc4769 | |||
| 5c1559dd78 | |||
| 9dbe74bdb1 | |||
| 6e676fc1e4 | |||
| 94314e332d | |||
| 383539bd25 | |||
| 8510924ebb | |||
| 772d428158 | |||
| 1579aa58d5 | |||
| 5e22d3e9a9 | |||
| 6e03375ec7 | |||
| 4c44df46f8 | |||
| af1cdccda2 | |||
| 4cb1719ee4 | |||
| 143d45904c | |||
| 4a2eab76e7 | |||
| d9ac85abb2 | |||
| c61b49c353 | |||
| 6348f9e2e1 | |||
| 54c244e4ea | |||
| 6f69641443 | |||
| 35451a05de | |||
| 2079b8f926 | |||
| 8c26ef848a | |||
| 52272d6119 | |||
| e5b3a29c5f | |||
| dea363e813 | |||
| 2b9ed29ebd | |||
| 753ecf6b6a | |||
| c05ce62df0 | |||
| 8e4027947b | |||
| b5e2f9fca8 | |||
| 1e8423b339 | |||
| ac61196fc6 | |||
| c592ac6655 | |||
| 09cb17e6ab | |||
| 65f5683b12 | |||
| 44116e4907 | |||
| d34907eccf | |||
| f785637cd5 | |||
| 91eb03fbb6 | |||
| 566e17719c | |||
| e93e3e0554 | |||
| 974ac5ef65 | |||
| 7045db7d70 | |||
| 2364fbdc09 | |||
| 1fbbd7cd70 | |||
| b4c15eae12 | |||
| 98610e20dc | |||
| da575f3657 | |||
| 55e473d04e | |||
| ac988c62e9 | |||
| 551d0eb161 | |||
| 2e817a9a6b | |||
| 73438b672a | |||
| c92177402d | |||
| 641dc78903 | |||
| ee812a86c6 | |||
| fe48c11cd6 | |||
| a57708d936 | |||
| f1debb91c6 | |||
| 06b96bd2c1 | |||
| 7889ab4778 | |||
| 2af18a85e5 | |||
| ea383871ab | |||
| e4c7d211b5 | |||
| af6a8f5546 | |||
| e9e5c83ab3 | |||
| e57cb4f562 | |||
| 6625f6f7fe | |||
| abd88c0c01 | |||
| 9f69cc92fe | |||
| 74aa9b1136 | |||
| bec0223899 | |||
| 89087a3dfd | |||
| 2a55f8cd32 | |||
| a1f91fd676 | |||
| 33dafd9bba | |||
| e098445588 | |||
| 35086ec63e | |||
| d66cf696fe | |||
| fbb0f2485f | |||
| 5c95a70aaf | |||
| 2e6aaf16d3 | |||
| dbc6365749 | |||
| c9d632c8b6 | |||
| bed1a33e68 | |||
| d4ed8cbeaa | |||
| dc64921e0e | |||
| bce448937b | |||
| 32a978d932 | |||
| 3737c535ff | |||
| 58b8bc1a68 | |||
| 1357ae8ff1 | |||
| b9a6f63cfa | |||
| fe88e20350 | |||
| aaa8e54c80 | |||
| 766dbee75a | |||
| 2e2c90983a | |||
| 518244f6bf | |||
| 809db67efd | |||
| ac79a0da69 | |||
| 45033fbeae | |||
| 1769fa8a5a | |||
| 4cc7fc309b | |||
| f966df4fb1 | |||
| ff141a5d47 | |||
| 781c4c551d | |||
| ac0566aa17 | |||
| 66b2429b2e | |||
| 325670e540 | |||
| 8d84523c32 | |||
| 851082dc6e | |||
| dcb548ee8e | |||
| 330e7d8d29 | |||
| 3ab4b94a8a | |||
| 09e88da1a9 | |||
| 773fbceff7 | |||
| 84e2292ed0 | |||
| 59ea4306b8 | |||
| b1edf54403 | |||
| a0654b9b59 | |||
| fe35bcbe1c | |||
| fcf11a1c36 | |||
| 05db16677d | |||
| 330cfdc259 | |||
| 6cad7bcbef | |||
| bf9463b24a | |||
| d69603f87b | |||
| 173daa7305 | |||
| b9c37f2845 | |||
| 8237f5d885 | |||
| 76432cf253 | |||
| c1c41ebde1 | |||
| 9082e93826 | |||
| 63cebd27cd | |||
| 505a0bd0b0 | |||
| fb6ec68046 | |||
| 5121e2fb0c | |||
| 25c9ee5b3c | |||
| a6014a6ad5 | |||
| ea7a5cf044 | |||
| cef1568e33 | |||
| 315ece38ae | |||
| 6fbc6c6680 | |||
| 7895eb8c38 | |||
| 164ebf47be | |||
| 1c85ba266f | |||
| e34a55c60a | |||
| 9bd4c49be9 | |||
| c1eb553cc0 | |||
| 04f11ecafa | |||
| 2d7fff06fe | |||
| 1d2481e558 | |||
| 19dff546b4 | |||
| 507e910e72 | |||
| 6b68998e93 | |||
| aad70d65b8 | |||
| f670c7f58f | |||
| 3ec3b4a793 | |||
| d92cee59a0 | |||
| 350eeae8da | |||
| 40fee72161 | |||
| 51d802d1c7 | |||
| ee32b97fbb | |||
| ee1a0033ac | |||
| a615dacf64 | |||
| 34de9cc10b | |||
| 03d04d5aa7 | |||
| 05ca3e147f | |||
| 0b1bae6ea1 | |||
| c056485471 | |||
| d152d0fd12 | |||
| 5bc5974bdb | |||
| 5e870e4c8a | |||
| 8178d505c3 | |||
| 78a52df0ec | |||
| 0ecab55959 | |||
| b3f056b933 | |||
| 520973b578 | |||
| d4ab7f96f1 | |||
| 911ab8ff4c | |||
| 9596060972 | |||
| 4b7971899c | |||
| 6eeedf858d | |||
| 7045d76039 | |||
| 90266175a1 | |||
| c810d0d957 | |||
| a4990f1944 | |||
| 52054f8b46 | |||
| 489c806292 | |||
| db69105813 | |||
| d39e532e4b | |||
| 25575b4598 | |||
| 769598d40a | |||
| 227f69befb | |||
| 9d760a515a | |||
| 26663100b4 | |||
| 7f6ec5ecdf | |||
| a58c3dbb25 | |||
| be0616b524 | |||
| 92390f9ad7 | |||
| 3e9221f6af | |||
| 1ad651fe72 | |||
| e4501852d5 | |||
| 402b226080 | |||
| c772b9a4df | |||
| 95cd4123b5 | |||
| 4164491bc2 | |||
| 0492a21bcf | |||
| 50d54d44c7 | |||
| 1cc1d2ddf9 | |||
| 0dda60cd81 | |||
| 114c927028 | |||
| e18ed7e0fd | |||
| 3e533cdee2 | |||
| 6e3b2c36fe | |||
| 73028f85a9 | |||
| d0d894a309 | |||
| db92f898d3 | |||
| 740a2fb2ff | |||
| 84e34e5490 | |||
| 2a05d77d94 | |||
| 97d7f6d7cb | |||
| 4f23d7e58b | |||
| ccf1e6fe1a | |||
| 7068dc7089 | |||
| 7f6f366e51 | |||
| 4d997ef03b | |||
| 65042e7210 | |||
| 89e17b0ffa | |||
| 6e52afef79 | |||
| a37d9aedc4 | |||
| fbd1f05eb9 | |||
| 3efbea8263 | |||
| 582c8dbc98 | |||
| 1e0d84fb8b | |||
| ca2faf0095 | |||
| e0c110ee55 | |||
| db2f803023 | |||
| d7f444c659 | |||
| 3b7f5c7c68 | |||
| 3072d56417 | |||
| 59bff7a0d5 | |||
| fadd781fc3 | |||
| b5518babac | |||
| cd28f8051b | |||
| 4966d8ecef | |||
| 3b7d22e675 | |||
| 6ceec8e737 | |||
| 077c59f761 | |||
| fd9829bedd | |||
| 1d79c43782 | |||
| c029117674 | |||
| ac78e160df | |||
| eb75362307 | |||
| 6c3e7b6256 | |||
| 7a3d61374f | |||
| ec78c45ea0 | |||
| 1337f93c33 | |||
| 1f3875f39c | |||
| f735befd52 | |||
| 2a9e21538f | |||
| 8ac1e58ef6 | |||
| c97b1b5edf | |||
| e773b251b9 | |||
| 9c8347aa72 | |||
| fcaa23fd77 | |||
| 5fa3eb35e4 | |||
| 67a900701e | |||
| 8314d647d9 | |||
| b88ca3ed91 | |||
| ee43e82235 | |||
| ce866bd987 | |||
| 95f103b4fd | |||
| 5ad6db9232 | |||
| c1cd6557eb | |||
| 8c3cbe6646 | |||
| add94ed70a | |||
| 6fe9dc899b | |||
| 6fedba093b | |||
| fb15c636eb | |||
| 043403b1e0 | |||
| 13eda7f68f | |||
| 9b94b1d72b | |||
| 07db413f2c | |||
| db67744b19 | |||
| 1292cdb145 | |||
| 7fa42d16eb | |||
| a673f9f206 | |||
| 8706f7ed06 | |||
| 883ce22c5b | |||
| 9ba1ea203b | |||
| 2cef804a34 | |||
| 46ce9eaff7 | |||
| d4cba72118 | |||
| 708d6d5aed | |||
| ff7af13080 | |||
| f325ea6b92 | |||
| d42a75cea9 | |||
| f4d4556ced | |||
| d79d57fcaa | |||
| 9712322597 | |||
| e3038b2b57 | |||
| ba1ebd18e0 | |||
| 9c70c64825 | |||
| ddc988a10d | |||
| aa4d759c38 | |||
| d94dcea7ee | |||
| 3d98ed7b6c | |||
| 18ac7e90ed | |||
| b04b62ed27 | |||
| 4066b4db42 | |||
| 037f05f323 | |||
| 3f358a6929 | |||
| e79a63588a | |||
| 60f15d57bd | |||
| e422ce0a13 | |||
| 598d68a822 | |||
| f3900f3659 | |||
| fa2d881442 | |||
| 435b7ae7e9 | |||
| 950a1ec0f8 | |||
| b483e15d5d | |||
| 4d67d11a39 | |||
| 1e719a8861 | |||
| d856911a1d | |||
| d50666df0c | |||
| f6bf239cc2 | |||
| bd5c1dd357 | |||
| 8a558746fe | |||
| df23148ccf | |||
| cf62a7ba12 | |||
| bfad7d1087 | |||
| 71d23f44c2 | |||
| f57e485ceb | |||
| 0531b716cc | |||
| bfb91c58c8 | |||
| 57eab1bc85 | |||
| 6fb438a994 | |||
| 23e0aee0a1 | |||
| 2f98568edf | |||
| 2b2bd5b688 | |||
| d05924e070 | |||
| e02c90457e | |||
| 6f3d7b8f49 | |||
| 712ca4bbb2 | |||
| 62b137b3b9 | |||
| 6b508e829f | |||
| 4e7bd2d88f | |||
| b584316caa | |||
| 220ef1b0ef | |||
| 38e747104e | |||
| 4fc2e3bc0d | |||
| 7f3ea6af1a | |||
| fc7635dc27 | |||
| e8b6cabfe3 | |||
| d29ea67f9b | |||
| c241ecfa36 | |||
| 3553f88909 | |||
| 6f5b44d016 | |||
| d5a0e7d355 | |||
| c3ce96afe9 | |||
| f15588f617 | |||
| 0bb6959755 | |||
| efb4660fe7 | |||
| 27c61cb194 | |||
| cddc61fe57 | |||
| 489ea16749 | |||
| b59b9fa4a5 | |||
| 57d2cab98a | |||
| 5c697a3a4d | |||
| 21f0c3e2ac | |||
| 42ab21c133 | |||
| a39eed123e | |||
| 1954895e16 | |||
| f6ad70bff9 | |||
| b9747669d3 | |||
| cbac85fbf4 | |||
| 76f503a664 | |||
| a8eb5ec4c2 | |||
| 56a8118ea3 | |||
| 234bf7d0a0 | |||
| ac8f04959e | |||
| 016f5829a5 | |||
| d79ab40988 | |||
| f0a1ce50bc | |||
| ef82d4327c | |||
| 8da639cd11 | |||
| 428f202cbc | |||
| 073ef2776e | |||
| 091104be8f | |||
| 8bbe771229 | |||
| 0ead20764b | |||
| 8f451283dc | |||
| 6ae5142166 | |||
| a8e46d34df | |||
| 20fe59b9ba | |||
| d0902218d4 | |||
| 38517e41ba | |||
| 507f37fcec | |||
| 06c645cf56 | |||
| b63494f686 | |||
| f78ce3f9b6 | |||
| 671e55f4d5 | |||
| 85d147ef62 | |||
| cb69182e02 | |||
| 8dd8873a03 | |||
| 4e55c6c395 | |||
| 381e85f0bc | |||
| 8ef95ac522 | |||
| 59133ed862 | |||
| 65aa1ba29d | |||
| c71f494974 | |||
| ae25f335f4 | |||
| 3101bb6980 | |||
| 4a241bb59a | |||
| 63f30bf9d0 | |||
| b39ccf26b6 | |||
| 21834fde41 | |||
| b6db1bf1af | |||
| 0b3ce4739b | |||
| f262b2924b | |||
| 724af12e03 | |||
| 3a59f3cef9 | |||
| 6aba3f08f1 | |||
| 8cd2be7edc | |||
| d768eff303 | |||
| ddf0d762d7 | |||
| aa207e2c13 | |||
| c192afe9eb | |||
| 415ba8185e | |||
| fdbb2549f8 | |||
| 3f5e219b1c | |||
| e6350a0ea4 | |||
| 56858c4109 | |||
| 509b222052 | |||
| 29e1488714 | |||
| 37a493439d | |||
| ab02eec824 | |||
| efb900a538 | |||
| 9d969904f2 | |||
| 2b5b61ea26 | |||
| 287d2b31a5 | |||
| de63fff1a1 | |||
| d2f8ffdc2b | |||
| 4143e53598 | |||
| 265ef22838 | |||
| ca28ce9e19 | |||
| f0cf92b542 | |||
| aceb6df848 | |||
| ec849cfc0c | |||
| 233dc5ce9d | |||
| e4cfab537e | |||
| 9fc01cee73 | |||
| 842c0b10b9 | |||
| f10a403045 | |||
| 3ab41835a2 | |||
| 0b41badab0 | |||
| 028cc66db3 | |||
| 60dc3b4650 | |||
| 3d9181a913 | |||
| 1633f54f4f | |||
| 86d27b06df | |||
| 07bb0a69fa | |||
| 138814f26c | |||
| 06540f5ce1 | |||
| 4feadb6352 | |||
| fdf49ff804 | |||
| eb49ef1013 | |||
| 17deec7720 | |||
| 76c2eac3bd | |||
| f095dd114e | |||
| 5004f6d07a | |||
| 31fbe3c265 | |||
| 478fe628ca | |||
| fbb675e815 | |||
| 96f5d45877 | |||
| 5d356847fa | |||
| 16ddb50159 | |||
| 48bead3ac9 | |||
| 35cff8dfcd | |||
| 2850c71317 | |||
| 534a4aedc7 | |||
| 27b55a7e60 | |||
| dc5660728e | |||
| 86693ab983 | |||
| 10d31abbf8 | |||
| c4779e3428 | |||
| 8bb462bda1 | |||
| 427b83875a | |||
| 8393910016 | |||
| e283d162e5 | |||
| d869d427e4 | |||
| a08bbc98db | |||
| c02665f71e | |||
| 9ddbaf42d5 | |||
| bc77ddadfa | |||
| 688376f757 | |||
| 1c0eab2fa2 | |||
| fb456c0bd0 | |||
| 8d0afbd21e | |||
| 528ba87492 | |||
| d08a9965c8 | |||
| bf360473d5 | |||
| fd2f565af2 | |||
| 7a3f16548b | |||
| 6fdc4a6434 | |||
| b5c5ea3193 | |||
| 1371b94599 | |||
| 2c986efcb1 | |||
| 9b8b0bff1d | |||
| cd94ecde19 | |||
| 385e41a920 | |||
| 836cff226a | |||
| 86861ccb70 | |||
| 0b4f10cfe6 | |||
| 8ac76cb152 | |||
| 818389839f | |||
| 3a069f18e2 | |||
| 31d0aa7fe4 | |||
| 256afa3be9 | |||
| 777c40bd97 | |||
| 9deaad8a2a | |||
| 1ad60582ba | |||
| 2aaf2465e4 | |||
| 0fb9c352d6 | |||
| ad289e531b | |||
| 2fbf2456ec | |||
| 2c2ecfb785 | |||
| 9478460893 | |||
| 6ae04b7ed3 | |||
| fff78cfade | |||
| e8472e07f9 | |||
| 2c0a4b11db | |||
| 1eee313e6e | |||
| 14dfa4795a | |||
| d7ed6b5a13 | |||
| f4a62fd64c |
@@ -0,0 +1,2 @@
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
+10
-2
@@ -1,3 +1,11 @@
|
||||
/target
|
||||
/targetString
|
||||
**/*.rs.bk
|
||||
/.idea/
|
||||
/*/target
|
||||
/test_inbox
|
||||
.idea
|
||||
target
|
||||
.env
|
||||
/.vscode/settings.json
|
||||
validator/.vscode
|
||||
sample-configs/validator-config.toml
|
||||
.vscode
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
jobs:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
fast_finish: true
|
||||
before_script:
|
||||
- rustup component add rustfmt
|
||||
script:
|
||||
- cargo build
|
||||
- cargo test -- --test-threads=1
|
||||
- cargo fmt -- --check
|
||||
+376
-12
@@ -1,12 +1,376 @@
|
||||
# nym-client Changelog
|
||||
|
||||
* removed the `--local` flag
|
||||
* introduced `--directory` argument to support arbitrary directory servers. Leaving it out will point the node at the "https://directory.nymtech.net" alpha testnet server
|
||||
* IPv6 support
|
||||
* client version number is now shown at node start
|
||||
* directory server location is now shown at node start
|
||||
* decrease default delays
|
||||
|
||||
## 0.1.0 - Initial Release
|
||||
|
||||
* The bare minimum set of features required by a Nym Client
|
||||
# Changelog
|
||||
|
||||
## [v0.7.0](https://github.com/nymtech/nym/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.6.0...HEAD)
|
||||
|
||||
The main features of this release are:
|
||||
|
||||
* the addition of gateway nodes
|
||||
* the retiring of the store-and-forward providers in favour of gateway nodes
|
||||
* got rid of TCP connections for clients, everything now happens through websockets
|
||||
* a new [Nym webassembly client](https://www.npmjs.com/package/@nymproject/nym-client-wasm), making it possible interact with Nym easily in browser-based runtimes
|
||||
* reliability and performance improvements for mixnode networking
|
||||
* initial validator code running (little functionality yet though)
|
||||
|
||||
See the [changelog](https://github.com/nymtech/nym/blob/develop/CHANGELOG.md) for detailed release notes.
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Use tokio codecs for multi\_tcp\_client [\#207](https://github.com/nymtech/nym/issues/207)
|
||||
- Consider rewriting sfw\_provider\_requests using tokio Framed + Codec [\#181](https://github.com/nymtech/nym/issues/181)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Unexplained traffic increase in presence of unroutable node [\#232](https://github.com/nymtech/nym/issues/232)
|
||||
- Gateway won't send to restarted layer1 nodes [\#231](https://github.com/nymtech/nym/issues/231)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Move to userpubkey@gatewaypubkey addresses. [\#235](https://github.com/nymtech/nym/issues/235)
|
||||
- Get `start\_local\_network.sh` working with the js example [\#227](https://github.com/nymtech/nym/issues/227)
|
||||
- Fix indeterminate test failure [\#218](https://github.com/nymtech/nym/issues/218)
|
||||
- Remove 'fetch' mechanism from desktop client's client in favour of push [\#211](https://github.com/nymtech/nym/issues/211)
|
||||
- Mixnode - load Sphinx keys like Gateway [\#209](https://github.com/nymtech/nym/issues/209)
|
||||
- Publish NPM package for WebAssembly client [\#206](https://github.com/nymtech/nym/issues/206)
|
||||
- Change --sockettype option on desktop client [\#203](https://github.com/nymtech/nym/issues/203)
|
||||
- Remove TCP sockets from desktop client [\#202](https://github.com/nymtech/nym/issues/202)
|
||||
- Desktop client currently hard-codes first provider [\#198](https://github.com/nymtech/nym/issues/198)
|
||||
- Webassembly client currently hard-codes first provider [\#197](https://github.com/nymtech/nym/issues/197)
|
||||
- Add Rust-based route construction to wasm client [\#196](https://github.com/nymtech/nym/issues/196)
|
||||
- Remove fetch event [\#195](https://github.com/nymtech/nym/issues/195)
|
||||
- Control messages should all be JSON [\#194](https://github.com/nymtech/nym/issues/194)
|
||||
- Desktop Client should attach to gateway websocket [\#193](https://github.com/nymtech/nym/issues/193)
|
||||
- Merge gateway and provider nodes [\#192](https://github.com/nymtech/nym/issues/192)
|
||||
- Remove direct Sphinx dependencies [\#184](https://github.com/nymtech/nym/issues/184)
|
||||
- \[Windows\] Presence notification fill OS socket queue [\#170](https://github.com/nymtech/nym/issues/170)
|
||||
- Figure out connection hiccups between client and provider [\#162](https://github.com/nymtech/nym/issues/162)
|
||||
- Improve the healthchecker [\#160](https://github.com/nymtech/nym/issues/160)
|
||||
- Rethink client addressability [\#135](https://github.com/nymtech/nym/issues/135)
|
||||
- Give some love to the service provider client ledger [\#116](https://github.com/nymtech/nym/issues/116)
|
||||
- Start Gateway node type [\#80](https://github.com/nymtech/nym/issues/80)
|
||||
- Bring health-checker into validator mix-mining [\#78](https://github.com/nymtech/nym/issues/78)
|
||||
- Solidify TCPSocket on client [\#72](https://github.com/nymtech/nym/issues/72)
|
||||
- scripts: run\_local\_network.sh doesn't die nicely [\#45](https://github.com/nymtech/nym/issues/45)
|
||||
- WASM version of the Sphinx packet [\#19](https://github.com/nymtech/nym/issues/19)
|
||||
- Persistent socket connection \(TCP Socket with provider\) [\#18](https://github.com/nymtech/nym/issues/18)
|
||||
- Persistent socket connection \(Websocket with client\) [\#17](https://github.com/nymtech/nym/issues/17)
|
||||
- Persistent socket connection \(TCP Socket with client\) [\#13](https://github.com/nymtech/nym/issues/13)
|
||||
- Persistent socket connection \(Websocket with client\) [\#12](https://github.com/nymtech/nym/issues/12)
|
||||
- Persistently store ledger with registered clients and their auth tokens [\#6](https://github.com/nymtech/nym/issues/6)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- renaming desktop to native client [\#251](https://github.com/nymtech/nym/pull/251) ([futurechimp](https://github.com/futurechimp))
|
||||
- Adding a pipenv dependencies file to the python client example [\#250](https://github.com/nymtech/nym/pull/250) ([futurechimp](https://github.com/futurechimp))
|
||||
- Cleaning up startup messages in native client [\#249](https://github.com/nymtech/nym/pull/249) ([futurechimp](https://github.com/futurechimp))
|
||||
- fixing up readme, bumping version number [\#246](https://github.com/nymtech/nym/pull/246) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/sphinx socket packet encoder [\#245](https://github.com/nymtech/nym/pull/245) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Adding some documentation to the webassembly client [\#244](https://github.com/nymtech/nym/pull/244) ([futurechimp](https://github.com/futurechimp))
|
||||
- Simplified some names and used the published npm package [\#242](https://github.com/nymtech/nym/pull/242) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/make andrew happy [\#241](https://github.com/nymtech/nym/pull/241) ([futurechimp](https://github.com/futurechimp))
|
||||
- Removed redundant console.log [\#240](https://github.com/nymtech/nym/pull/240) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/explicit gateway addressing [\#239](https://github.com/nymtech/nym/pull/239) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/clean up [\#238](https://github.com/nymtech/nym/pull/238) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/addressing update [\#237](https://github.com/nymtech/nym/pull/237) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Added hidden init flag to increase default traffic volume [\#234](https://github.com/nymtech/nym/pull/234) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bugfix/issue\#231 [\#233](https://github.com/nymtech/nym/pull/233) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Fixed unwrap on none value [\#230](https://github.com/nymtech/nym/pull/230) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bugfix/gateway crash on incomplete ws handshake [\#229](https://github.com/nymtech/nym/pull/229) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/start local network improvements [\#228](https://github.com/nymtech/nym/pull/228) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Updated directory\_client reqwest to 0.10 [\#226](https://github.com/nymtech/nym/pull/226) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Updated js-example to get gateway from topology [\#225](https://github.com/nymtech/nym/pull/225) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Requiring explicit timestamp when converting from rest to service mix… [\#224](https://github.com/nymtech/nym/pull/224) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/minor docs fixes [\#223](https://github.com/nymtech/nym/pull/223) ([futurechimp](https://github.com/futurechimp))
|
||||
- Removed having to care about SURB\_ID [\#222](https://github.com/nymtech/nym/pull/222) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Moved relevant parts of old mix-client to nymsphinx [\#221](https://github.com/nymtech/nym/pull/221) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/load keys on run [\#220](https://github.com/nymtech/nym/pull/220) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Updated wasm code to work with new gateway and updated the example [\#219](https://github.com/nymtech/nym/pull/219) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- validator: removing health checker [\#217](https://github.com/nymtech/nym/pull/217) ([futurechimp](https://github.com/futurechimp))
|
||||
- The great sfw-provider purge of 2020 [\#216](https://github.com/nymtech/nym/pull/216) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Fixed compilation warnings on unreachable code when compiling with fe… [\#215](https://github.com/nymtech/nym/pull/215) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/healthchecker removal [\#214](https://github.com/nymtech/nym/pull/214) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bugfix/send to correct gateway [\#213](https://github.com/nymtech/nym/pull/213) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/client socket adjustments [\#212](https://github.com/nymtech/nym/pull/212) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Sending sphinx packet independent of the receiver task [\#210](https://github.com/nymtech/nym/pull/210) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/gateway provider merge [\#208](https://github.com/nymtech/nym/pull/208) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/route from topology [\#201](https://github.com/nymtech/nym/pull/201) ([futurechimp](https://github.com/futurechimp))
|
||||
- Intermediate gateway-heart surgery checkpoint [\#199](https://github.com/nymtech/nym/pull/199) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/wasm js demo [\#191](https://github.com/nymtech/nym/pull/191) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/improve js example [\#190](https://github.com/nymtech/nym/pull/190) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/limit direct sphinx dependency + remove direct curve25519 dependency from wasm client [\#189](https://github.com/nymtech/nym/pull/189) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/very minor refactoring [\#188](https://github.com/nymtech/nym/pull/188) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/persistent ledger [\#187](https://github.com/nymtech/nym/pull/187) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Optimising wasm build size, shaves about 10% size off our wasm output. [\#186](https://github.com/nymtech/nym/pull/186) ([futurechimp](https://github.com/futurechimp))
|
||||
- Ran `npm audit fix` on the wasm demo directory. [\#185](https://github.com/nymtech/nym/pull/185) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/nym sphinx wasm [\#183](https://github.com/nymtech/nym/pull/183) ([futurechimp](https://github.com/futurechimp))
|
||||
- Improvements to sfw-provider - client communcation [\#180](https://github.com/nymtech/nym/pull/180) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Adding Apache 2 license headers to all files [\#178](https://github.com/nymtech/nym/pull/178) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/service persistence [\#171](https://github.com/nymtech/nym/pull/171) ([futurechimp](https://github.com/futurechimp))
|
||||
|
||||
## [v0.6.0](https://github.com/nymtech/nym/tree/v0.6.0) (2020-04-07)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.5.0...v0.6.0)
|
||||
|
||||
This release fixes bugs in v0.5.0. All testnet node operators are advised to upgrade from v0.5.0.
|
||||
|
||||
* fixed premature EOFs on socket connections by using the new multi-TCP client
|
||||
* fixed a bug causing client and mixnode connection hangs for misconfigured nodes
|
||||
* by default 'Debug' section of saved configs is now empty and default values are used unless explicitly overridden
|
||||
* introduced packet chunking allowing clients to send messages of arbitrary length. Note that packet retransmission is not implemented yet, so for longer messages, you might not get anything
|
||||
* mixnodes now periodically log stats regarding number of packets mixed
|
||||
* fixed possible client hang ups when sending high rates of traffic
|
||||
* preventing mixes from starting with same announce-host as an existing node
|
||||
* fixed overflow multiplication if connection backoff was set to a high value
|
||||
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Periodic activity summary [\#172](https://github.com/nymtech/nym/issues/172)
|
||||
- Move contents of 'common/addressing' into 'common/nymsphinx' [\#161](https://github.com/nymtech/nym/issues/161)
|
||||
- Make builds simpler for node operators [\#114](https://github.com/nymtech/nym/issues/114)
|
||||
- Chunking in `nym-client` \(receive\) [\#83](https://github.com/nymtech/nym/issues/83)
|
||||
- Chunking in `nym-client` \(send\) [\#82](https://github.com/nymtech/nym/issues/82)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Feature/tcp client connection timeout [\#176](https://github.com/nymtech/nym/pull/176) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/mixing stats logging [\#175](https://github.com/nymtech/nym/pull/175) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Preventing multiplication overflow for reconnection backoff [\#174](https://github.com/nymtech/nym/pull/174) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/non mandatory debug config [\#173](https://github.com/nymtech/nym/pull/173) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/addressing move [\#169](https://github.com/nymtech/nym/pull/169) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Checking if any other node is already announcing the same host [\#168](https://github.com/nymtech/nym/pull/168) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bugfix/closing tcp client connections on drop [\#167](https://github.com/nymtech/nym/pull/167) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Yielding tokio task upon creating loop/real traffic message [\#166](https://github.com/nymtech/nym/pull/166) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/minor healthchecker improvements [\#165](https://github.com/nymtech/nym/pull/165) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/packet chunking [\#158](https://github.com/nymtech/nym/pull/158) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.5.0](https://github.com/nymtech/nym/tree/v0.5.0) (2020-03-23)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.5.0-rc.1...v0.5.0)
|
||||
|
||||
1. Introduced proper configuration options for mixnodes, clients and providers. Everything is initialised with the `init` command that creates a saved config.toml file. To run the binary you now use `nym-<binary-name> run`, for example `nym-mixnode run`. Each flag can be overwritten at any stage with the following priority: run flags, data in config.toml and finally init flags.
|
||||
2. Made mixnet TCP connections persistent. When sending a Sphinx packet, it should no longer go through the lengthy process of establishing a TCP connection only to immediately tear it down after sending a single packet. This significantly boosts throughput.
|
||||
3. A lot of work on code clean up and refactoring including some performance fixes.
|
||||
4. Client now determines its default nym-sfw-provider at startup and should always try to connect to the same one. Note: we still can't reliably run more than a single provider on the network.
|
||||
5. Logging messages now have timestamps and when running at more aggressive log mode (like debug or even trace) we should no longer be overwhelmed with messages from external crates.
|
||||
6. Initial compatibility with Windows. Please let us know if you have problems.
|
||||
7. More work on validator, including initial Tendermint integration in Rust, and the start of the mixmining system.
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Introduce timestamps to log messages [\#124](https://github.com/nymtech/nym/issues/124)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- removing spooky startup warning message [\#155](https://github.com/nymtech/nym/pull/155) ([futurechimp](https://github.com/futurechimp))
|
||||
- Some more startup fixes [\#154](https://github.com/nymtech/nym/pull/154) ([futurechimp](https://github.com/futurechimp))
|
||||
- Entering runtime context when creating mix traffic controller [\#153](https://github.com/nymtech/nym/pull/153) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Friendlification of startup messages [\#151](https://github.com/nymtech/nym/pull/151) ([futurechimp](https://github.com/futurechimp))
|
||||
- Entering runtime context when creating packet forwarder [\#150](https://github.com/nymtech/nym/pull/150) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/add topology to validator [\#149](https://github.com/nymtech/nym/pull/149) ([futurechimp](https://github.com/futurechimp))
|
||||
- Making code work on windows machines [\#148](https://github.com/nymtech/nym/pull/148) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- validator: adding HTTP interface [\#146](https://github.com/nymtech/nym/pull/146) ([futurechimp](https://github.com/futurechimp))
|
||||
- Extracting the log setup [\#145](https://github.com/nymtech/nym/pull/145) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/optional location in configs [\#144](https://github.com/nymtech/nym/pull/144) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/concurrent connection managers [\#142](https://github.com/nymtech/nym/pull/142) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Defaulting for global 'Info' logging level if not set in .env [\#140](https://github.com/nymtech/nym/pull/140) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Provider not storing loop cover messages [\#139](https://github.com/nymtech/nym/pull/139) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Using log builder to include timestamps + filters [\#138](https://github.com/nymtech/nym/pull/138) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/client ws refactoring [\#134](https://github.com/nymtech/nym/pull/134) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bugfix/metrics presence delay fix [\#133](https://github.com/nymtech/nym/pull/133) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Removed outdated and redundant sample-configs [\#131](https://github.com/nymtech/nym/pull/131) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- If not overridden, 'announce-host' should default to 'host' [\#130](https://github.com/nymtech/nym/pull/130) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Nice to know who we're talking to at startup... [\#129](https://github.com/nymtech/nym/pull/129) ([futurechimp](https://github.com/futurechimp))
|
||||
|
||||
## [v0.5.0-rc.1](https://github.com/nymtech/nym/tree/v0.5.0-rc.1) (2020-03-06)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.4.1...v0.5.0-rc.1)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- COMPILE: Could not compile project using Cargo [\#118](https://github.com/nymtech/nym/issues/118)
|
||||
- Wherever unbounded mpsc channel is used, prefer unbounded\_send\(\) over send\(\).await [\#90](https://github.com/nymtech/nym/issues/90)
|
||||
- Add a `Send` method in nym-client [\#81](https://github.com/nymtech/nym/issues/81)
|
||||
- Start on Tendermint integration [\#79](https://github.com/nymtech/nym/issues/79)
|
||||
- Ditch DummyKeyPair [\#75](https://github.com/nymtech/nym/issues/75)
|
||||
- Replace args with proper config files [\#69](https://github.com/nymtech/nym/issues/69)
|
||||
- Fix incorrectly used Arcs [\#47](https://github.com/nymtech/nym/issues/47)
|
||||
- nym-mixnode mandatory host option [\#26](https://github.com/nymtech/nym/issues/26)
|
||||
- Create config struct for mixnode \(possibly also for client\) [\#21](https://github.com/nymtech/nym/issues/21)
|
||||
- Reuse TCP socket connection between client and mixnodes [\#20](https://github.com/nymtech/nym/issues/20)
|
||||
- Once implementation is available, wherever appropriate, replace `futures::lock::Mutex` with `futures::lock::RwLock` [\#9](https://github.com/nymtech/nym/issues/9)
|
||||
- Check if RwLock on MixProcessingData is still needed [\#8](https://github.com/nymtech/nym/issues/8)
|
||||
- Reuse TCP socket connection between mixnodes and providers [\#3](https://github.com/nymtech/nym/issues/3)
|
||||
- Persistent socket connection with other mixes [\#2](https://github.com/nymtech/nym/issues/2)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Feature/client refactoring [\#128](https://github.com/nymtech/nym/pull/128) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/provider refactoring [\#125](https://github.com/nymtech/nym/pull/125) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- all: fixing mis-spelling [\#123](https://github.com/nymtech/nym/pull/123) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/further clippy fixes [\#121](https://github.com/nymtech/nym/pull/121) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/tokio tungstenite dependency fix [\#120](https://github.com/nymtech/nym/pull/120) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/config files cleanup [\#119](https://github.com/nymtech/nym/pull/119) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/config files [\#117](https://github.com/nymtech/nym/pull/117) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/un genericize keys [\#111](https://github.com/nymtech/nym/pull/111) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/abci [\#110](https://github.com/nymtech/nym/pull/110) ([futurechimp](https://github.com/futurechimp))
|
||||
- Simplified the use of generics on identity keypair by using output types [\#109](https://github.com/nymtech/nym/pull/109) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.4.1](https://github.com/nymtech/nym/tree/v0.4.1) (2020-01-29)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.4.0...v0.4.1)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Change healthcheck to run on provided topology rather than pull one itself [\#95](https://github.com/nymtech/nym/issues/95)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bugfix/healthcheck on provided topology [\#108](https://github.com/nymtech/nym/pull/108) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.4.0](https://github.com/nymtech/nym/tree/v0.4.0) (2020-01-28)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/0.4.0-rc.2...v0.4.0)
|
||||
|
||||
Nym 0.4.0 Platform
|
||||
|
||||
In this release, we're taking a lot more care with version numbers, so that we can ensure upgrade compatibility for mixnodes, providers, clients, and validators more easily.
|
||||
|
||||
This release also integrates a health-checker and network topology refresh into the Nym client, so that the client can intelligently choose paths which route around any non-functional or incompatible nodes.
|
||||
|
||||
## [0.4.0-rc.2](https://github.com/nymtech/nym/tree/0.4.0-rc.2) (2020-01-28)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.4.0-rc.2...0.4.0-rc.2)
|
||||
|
||||
## [v0.4.0-rc.2](https://github.com/nymtech/nym/tree/v0.4.0-rc.2) (2020-01-28)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.4.0-rc.1...v0.4.0-rc.2)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Hotfix/semver compatibility [\#106](https://github.com/nymtech/nym/pull/106) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.4.0-rc.1](https://github.com/nymtech/nym/tree/v0.4.0-rc.1) (2020-01-28)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.3.3...v0.4.0-rc.1)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Check Sphinx packet length in client [\#98](https://github.com/nymtech/nym/issues/98)
|
||||
- workflow test [\#97](https://github.com/nymtech/nym/issues/97)
|
||||
- Client SemVer [\#85](https://github.com/nymtech/nym/issues/85)
|
||||
- Move PemStore [\#74](https://github.com/nymtech/nym/issues/74)
|
||||
- Periodic client refresh [\#70](https://github.com/nymtech/nym/issues/70)
|
||||
- Logging [\#68](https://github.com/nymtech/nym/issues/68)
|
||||
- Nym-client refactor [\#67](https://github.com/nymtech/nym/issues/67)
|
||||
- Stop panicking! [\#66](https://github.com/nymtech/nym/issues/66)
|
||||
- Fix Mixnode Panic on Sphinx packet replay [\#65](https://github.com/nymtech/nym/issues/65)
|
||||
- Convert older code to start using logging [\#35](https://github.com/nymtech/nym/issues/35)
|
||||
- Convert to non-url-safe base64 \(everywhere\) [\#28](https://github.com/nymtech/nym/issues/28)
|
||||
- If a thread blows at startup, panic the entire application [\#15](https://github.com/nymtech/nym/issues/15)
|
||||
- Split provider/mod.rs [\#10](https://github.com/nymtech/nym/issues/10)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Feature/health checker with existing keys [\#105](https://github.com/nymtech/nym/pull/105) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/remove topology equality check [\#104](https://github.com/nymtech/nym/pull/104) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/base58 [\#102](https://github.com/nymtech/nym/pull/102) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/panic improvements [\#101](https://github.com/nymtech/nym/pull/101) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/fix sphinx unwraps [\#100](https://github.com/nymtech/nym/pull/100) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/check packet length [\#99](https://github.com/nymtech/nym/pull/99) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/version filtering improvements [\#96](https://github.com/nymtech/nym/pull/96) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/refreshing topology [\#94](https://github.com/nymtech/nym/pull/94) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/consistent logging [\#93](https://github.com/nymtech/nym/pull/93) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/semver client [\#92](https://github.com/nymtech/nym/pull/92) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/client refactor [\#91](https://github.com/nymtech/nym/pull/91) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Release builds should no longer silently fail - everything will be im… [\#89](https://github.com/nymtech/nym/pull/89) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.3.3](https://github.com/nymtech/nym/tree/v0.3.3) (2020-01-20)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.3.2...v0.3.3)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Nym client crashing and disconnecting local websocket with complaint about binary data [\#56](https://github.com/nymtech/nym/issues/56)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Websocket text fix [\#64](https://github.com/nymtech/nym/issues/64)
|
||||
- Restore nym-client lib in crate [\#63](https://github.com/nymtech/nym/issues/63)
|
||||
- Make websocket not crash on ping or pong messages [\#62](https://github.com/nymtech/nym/issues/62)
|
||||
- Messages returned by fetch are base64 encoded [\#55](https://github.com/nymtech/nym/issues/55)
|
||||
- Check layer 1 connectivity at client start [\#38](https://github.com/nymtech/nym/issues/38)
|
||||
- Check required sfw-provider args [\#27](https://github.com/nymtech/nym/issues/27)
|
||||
- Make Electron app work with new Rust mixnet client [\#16](https://github.com/nymtech/nym/issues/16)
|
||||
- Take version numbers into account when picking routes [\#14](https://github.com/nymtech/nym/issues/14)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Feature/websocket improvements [\#88](https://github.com/nymtech/nym/pull/88) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Using println rather than log for startup banner, it's not an error [\#87](https://github.com/nymtech/nym/pull/87) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/nym client lib [\#61](https://github.com/nymtech/nym/pull/61) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.3.2](https://github.com/nymtech/nym/tree/v0.3.2) (2020-01-17)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.3.1...v0.3.2)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Feature/separate presence address [\#59](https://github.com/nymtech/nym/pull/59) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.3.1](https://github.com/nymtech/nym/tree/v0.3.1) (2020-01-16)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.3.0...v0.3.1)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bugfix/presence client crash [\#58](https://github.com/nymtech/nym/pull/58) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.3.0](https://github.com/nymtech/nym/tree/v0.3.0) (2020-01-14)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.2.0...v0.3.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Error referring to mismatched types caused by topology [\#46](https://github.com/nymtech/nym/issues/46)
|
||||
- Provider needs to announce two of its addresses \(+ issue of hardcoded port\) [\#32](https://github.com/nymtech/nym/issues/32)
|
||||
- Port run\_network.sh from old Go mixnet [\#30](https://github.com/nymtech/nym/issues/30)
|
||||
- Health Checker inside Validator [\#29](https://github.com/nymtech/nym/issues/29)
|
||||
- Combine Rust repositories into a single master repo containing multiple crates [\#24](https://github.com/nymtech/nym/issues/24)
|
||||
- Fix the version numbers on provider and mixnode [\#23](https://github.com/nymtech/nym/issues/23)
|
||||
- Travis releases for Nym [\#22](https://github.com/nymtech/nym/issues/22)
|
||||
- Add version number to presence [\#4](https://github.com/nymtech/nym/issues/4)
|
||||
- Add version number to presence [\#1](https://github.com/nymtech/nym/issues/1)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Feature/client topology filtering [\#54](https://github.com/nymtech/nym/pull/54) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- print public key for nym client tools [\#53](https://github.com/nymtech/nym/pull/53) ([mileschet](https://github.com/mileschet))
|
||||
- Showing binding warning on binding to localhost, 0.0.0.0 or 127.0.0.1 [\#52](https://github.com/nymtech/nym/pull/52) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- validator: moving sample config files into sample configs directory [\#51](https://github.com/nymtech/nym/pull/51) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/validator health checker [\#50](https://github.com/nymtech/nym/pull/50) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Improving websocket connection errors [\#49](https://github.com/nymtech/nym/pull/49) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/crypto crate [\#48](https://github.com/nymtech/nym/pull/48) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/chill log messages [\#43](https://github.com/nymtech/nym/pull/43) ([futurechimp](https://github.com/futurechimp))
|
||||
- persistence: improving PEM file reading and parsing failure messages [\#42](https://github.com/nymtech/nym/pull/42) ([futurechimp](https://github.com/futurechimp))
|
||||
- Providing a nicer error than "failed on unwrap\(\)" when topology retri… [\#41](https://github.com/nymtech/nym/pull/41) ([futurechimp](https://github.com/futurechimp))
|
||||
- Prettying up sfw-provider start sequence a bit. [\#40](https://github.com/nymtech/nym/pull/40) ([futurechimp](https://github.com/futurechimp))
|
||||
- Removing the run command from code and documentation [\#39](https://github.com/nymtech/nym/pull/39) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/script/localnet [\#37](https://github.com/nymtech/nym/pull/37) ([futurechimp](https://github.com/futurechimp))
|
||||
- Feature/arguments fix [\#36](https://github.com/nymtech/nym/pull/36) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/crates cleanup [\#34](https://github.com/nymtech/nym/pull/34) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Features/version number to presence [\#25](https://github.com/nymtech/nym/pull/25) ([aniampio](https://github.com/aniampio))
|
||||
- Feature/minor client changes [\#7](https://github.com/nymtech/nym/pull/7) ([jstuczyn](https://github.com/jstuczyn))
|
||||
|
||||
## [v0.2.0](https://github.com/nymtech/nym/tree/v0.2.0) (2020-01-07)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/0.2.0...v0.2.0)
|
||||
|
||||
## [0.2.0](https://github.com/nymtech/nym/tree/0.2.0) (2020-01-06)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/0.1.0...0.2.0)
|
||||
|
||||
|
||||
|
||||
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
||||
|
||||
Generated
+2145
-975
File diff suppressed because it is too large
Load Diff
+22
-37
@@ -1,39 +1,24 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-client"
|
||||
version = "0.2.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
|
||||
[lib]
|
||||
name = "nym_client"
|
||||
path = "src/lib.rs"
|
||||
[workspace]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.11.0"
|
||||
clap = "2.33.0"
|
||||
curve25519-dalek = "1.2.3"
|
||||
dirs = "2.0.2"
|
||||
futures = "0.3.1"
|
||||
hex = "0.4.0"
|
||||
pem = "0.7.0"
|
||||
rand = "0.7.2"
|
||||
rand_distr = "0.2.2"
|
||||
reqwest = "0.9.22"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.44"
|
||||
sphinx = { path = "../sphinx" }
|
||||
sfw-provider-requests = { path = "../nym-sfw-provider/sfw-provider-requests" }
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tungstenite = "0.9.2"
|
||||
|
||||
# putting this explicitly below everything and most likely, the next time we look into it, it will already have a proper release
|
||||
tokio-tungstenite = { git = "https://github.com/dbcfd/tokio-tungstenite", rev="6dc2018cbfe8fe7ddd75ff977343086503135b38" }
|
||||
|
||||
[build-dependencies]
|
||||
built = "0.3.2"
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "0.22.0"
|
||||
members = [
|
||||
"clients/native",
|
||||
"clients/webassembly",
|
||||
"common/client-libs/directory-client",
|
||||
"common/client-libs/gateway-client",
|
||||
"common/client-libs/mixnet-client",
|
||||
"common/client-libs/validator-client",
|
||||
"common/config",
|
||||
"common/crypto",
|
||||
"common/healthcheck",
|
||||
"common/nymsphinx",
|
||||
"common/pemstore",
|
||||
"common/topology",
|
||||
"gateway",
|
||||
"gateway/gateway-requests",
|
||||
"mixnode",
|
||||
"validator",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,10 +1,24 @@
|
||||
# Nym Client
|
||||
## The Nym Privacy Platform
|
||||
|
||||
The Nym Client communicates with the remote, decentralised nodes which make up the Nym system as a whole.
|
||||
This repository contains the full Nym platform.
|
||||
|
||||
It needs to handle communication with 3 types of remote nodes. Here's the development status of each:
|
||||
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
|
||||
|
||||
- [ ] Directory nodes
|
||||
- [ ] Mix nodes
|
||||
- [ ] Validator nodes
|
||||
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
|
||||
* nym-client - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
|
||||
* nym-sfw-provider - a store-and-forward service provider. The provider acts sort of like a mailbox for mixnet messages.
|
||||
* nym-validator - currently just starting development. Handles consensus ordering of transactions, mixmining, and coconut credential generation and validation.
|
||||
|
||||
[](https://travis-ci.com/nymtech/nym)
|
||||
|
||||
### Building
|
||||
|
||||
Platform build instructions are available on [our docs site](https://nymtech.net/docs).
|
||||
|
||||
### Developing
|
||||
|
||||
There's a `.env.sample-dev` file provided which you can rename to `.env` if you want convenient logging, backtrace, or other environment variables pre-set. The `.env` file is ignored so you don't need to worry about checking it in.
|
||||
|
||||
### Developer chat
|
||||
|
||||
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
use built;
|
||||
|
||||
fn main() {
|
||||
built::write_built_file().expect("Failed to acquire build-time information");
|
||||
}
|
||||
Generated
+2402
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-client"
|
||||
version = "0.7.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "nym_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.3.0"
|
||||
clap = "2.33.0"
|
||||
curve25519-dalek = "2.0.0"
|
||||
dirs = "2.0.2"
|
||||
dotenv = "0.15.0"
|
||||
futures = "0.3.1"
|
||||
log = "0.4"
|
||||
pem = "0.7.0"
|
||||
pretty_env_logger = "0.3"
|
||||
reqwest = "0.9.22"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.44"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tokio-tungstenite = "0.10.1"
|
||||
url = "2.1"
|
||||
|
||||
## internal
|
||||
config = {path = "../../common/config"}
|
||||
crypto = {path = "../../common/crypto"}
|
||||
directory-client = { path = "../../common/client-libs/directory-client" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
mixnet-client = { path = "../../common/client-libs/mixnet-client" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
pemstore = {path = "../../common/pemstore"}
|
||||
topology = {path = "../../common/topology" }
|
||||
|
||||
[build-dependencies]
|
||||
built = "0.3.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
@@ -0,0 +1,3 @@
|
||||
# Nym Desktop Client
|
||||
|
||||
The Nym Desktop Client communicates with the remote, decentralised nodes which make up the Nym system as a whole.
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use built;
|
||||
|
||||
fn main() {
|
||||
built::write_built_file().expect("Failed to acquire build-time information");
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis efficitur neque. Quisque aliquet vulputate ante, eget vehicula odio feugiat ac. Nulla ut mattis magna. Aenean tincidunt quis nulla eget eleifend. Cras in pretium sem. Nunc lorem metus, blandit sit amet egestas ut, feugiat quis tellus. Aenean tristique, enim a tincidunt condimentum, eros est blandit nunc, id viverra metus erat at nulla. Vivamus at tellus sodales, feugiat odio vel, laoreet neque. Vivamus posuere nulla ac sodales bibendum.
|
||||
|
||||
Vestibulum pulvinar nisi non ultricies egestas. Integer finibus ultrices justo vitae suscipit. Etiam interdum eu justo vel interdum. Morbi sagittis ac nisl quis consequat. Mauris dapibus ut risus ac facilisis. Pellentesque non tortor feugiat, consectetur arcu vel, ullamcorper sapien. Proin sodales purus non orci bibendum, sit amet ultrices justo ullamcorper. Nullam ac risus ac justo ultricies efficitur auctor nec arcu. Etiam sed finibus felis. Suspendisse potenti. Phasellus malesuada velit ac ullamcorper egestas. Sed elementum diam ut est gravida ultricies.
|
||||
|
||||
Pellentesque sed metus massa. Cras imperdiet lacus sit amet dolor aliquam, luctus posuere justo hendrerit. Morbi augue ex, gravida a metus sed, scelerisque euismod lacus. Nam consequat sapien ac pellentesque sagittis. Morbi a ultrices massa, vel aliquet ex. Maecenas ac sem diam. Nunc sed erat et ipsum volutpat auctor. Etiam elit felis, commodo vitae ipsum ac, fermentum lobortis arcu. Aliquam eu tempus enim. Curabitur vulputate imperdiet aliquam. Morbi iaculis rhoncus risus at malesuada. Donec accumsan feugiat ligula ut facilisis. Nunc porttitor sit amet est eget malesuada. Sed sed consectetur augue, non dapibus orci. Mauris aliquam pellentesque quam, sit amet pellentesque velit cursus vitae. Morbi sit amet molestie risus.
|
||||
|
||||
Nam gravida non ligula a egestas. Fusce sodales, purus id rhoncus mattis, purus est vehicula urna, vel finibus augue velit et est. Donec dictum erat eleifend lobortis iaculis. Praesent id venenatis ante. Donec feugiat, ipsum eget porttitor pulvinar, nisl odio posuere lorem, ut placerat elit nulla a ligula. Suspendisse nec nibh tincidunt, sollicitudin mi a, volutpat ligula. In maximus quam lacus, eget semper dolor sagittis sit amet.
|
||||
|
||||
In vitae hendrerit est, quis facilisis dui. In eu ante enim. Nullam hendrerit odio sit amet odio tincidunt eleifend. Aliquam erat volutpat. Curabitur commodo, purus pharetra lobortis rhoncus, tortor massa imperdiet nisl, vel dignissim tortor sem at orci. Aliquam maximus lobortis lacus, eu porttitor purus dapibus ut. Praesent at dapibus felis, efficitur blandit tortor. In hac habitasse platea dictumst. Aenean ultrices, nisl a pretium sagittis, tellus sapien mollis erat, eu consectetur erat mauris sed libero. Duis feugiat dapibus mi, vel ornare velit vehicula mattis. Ut suscipit pharetra leo et sollicitudin.
|
||||
@@ -0,0 +1,9 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis efficitur neque. Quisque aliquet vulputate ante, eget vehicula odio feugiat ac. Nulla ut mattis magna. Aenean tincidunt quis nulla eget eleifend. Cras in pretium sem. Nunc lorem metus, blandit sit amet egestas ut, feugiat quis tellus. Aenean tristique, enim a tincidunt condimentum, eros est blandit nunc, id viverra metus erat at nulla. Vivamus at tellus sodales, feugiat odio vel, laoreet neque. Vivamus posuere nulla ac sodales bibendum.
|
||||
|
||||
Vestibulum pulvinar nisi non ultricies egestas. Integer finibus ultrices justo vitae suscipit. Etiam interdum eu justo vel interdum. Morbi sagittis ac nisl quis consequat. Mauris dapibus ut risus ac facilisis. Pellentesque non tortor feugiat, consectetur arcu vel, ullamcorper sapien. Proin sodales purus non orci bibendum, sit amet ultrices justo ullamcorper. Nullam ac risus ac justo ultricies efficitur auctor nec arcu. Etiam sed finibus felis. Suspendisse potenti. Phasellus malesuada velit ac ullamcorper egestas. Sed elementum diam ut est gravida ultricies.
|
||||
|
||||
Pellentesque sed metus massa. Cras imperdiet lacus sit amet dolor aliquam, luctus posuere justo hendrerit. Morbi augue ex, gravida a metus sed, scelerisque euismod lacus. Nam consequat sapien ac pellentesque sagittis. Morbi a ultrices massa, vel aliquet ex. Maecenas ac sem diam. Nunc sed erat et ipsum volutpat auctor. Etiam elit felis, commodo vitae ipsum ac, fermentum lobortis arcu. Aliquam eu tempus enim. Curabitur vulputate imperdiet aliquam. Morbi iaculis rhoncus risus at malesuada. Donec accumsan feugiat ligula ut facilisis. Nunc porttitor sit amet est eget malesuada. Sed sed consectetur augue, non dapibus orci. Mauris aliquam pellentesque quam, sit amet pellentesque velit cursus vitae. Morbi sit amet molestie risus.
|
||||
|
||||
Nam gravida non ligula a egestas. Fusce sodales, purus id rhoncus mattis, purus est vehicula urna, vel finibus augue velit et est. Donec dictum erat eleifend lobortis iaculis. Praesent id venenatis ante. Donec feugiat, ipsum eget porttitor pulvinar, nisl odio posuere lorem, ut placerat elit nulla a ligula. Suspendisse nec nibh tincidunt, sollicitudin mi a, volutpat ligula. In maximus quam lacus, eget semper dolor sagittis sit amet.
|
||||
|
||||
In vitae hendrerit est, quis facilisis dui. In eu ante enim. Nullam hendrerit odio sit amet odio tincidunt eleifend. Aliquam erat volutpat. Curabitur commodo, purus pharetra lobortis rhoncus, tortor massa imperdiet nisl, vel dignissim tortor sem at orci. Aliquam maximus lobortis lacus, eu porttitor purus dapibus ut. Praesent at dapibus felis, efficitur blandit tortor. In hac habitasse platea dictumst. Aenean ultrices, nisl a pretium sagittis, tellus sapien mollis erat, eu consectetur erat mauris sed libero. Duis feugiat dapibus mi, vel ornare velit vehicula mattis. Ut suscipit pharetra leo et sollicitudin.
|
||||
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
"github.com/gorilla/websocket"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getSelfAddress(conn *websocket.Conn) string {
|
||||
selfAddressRequest, err := json.Marshal(map[string]string{"type": "selfAddress"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = conn.WriteMessage(websocket.TextMessage, []byte(selfAddressRequest)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
responseJSON := make(map[string]interface{})
|
||||
err = conn.ReadJSON(&responseJSON)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return responseJSON["address"].(string)
|
||||
}
|
||||
|
||||
func main() {
|
||||
uri := "ws://localhost:1977"
|
||||
|
||||
conn, _, err := websocket.DefaultDialer.Dial(uri, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
selfAddress := getSelfAddress(conn)
|
||||
fmt.Printf("our address is: %v\n", selfAddress)
|
||||
|
||||
// we receive our address in string format of OUR_PUB_KEY @ OUR_GATE_PUB_KEY
|
||||
// both keys are 32 bytes and we need to encode them as binary without the '@' sign
|
||||
splitAddress := strings.Split(selfAddress, "@");
|
||||
decodedDestination := base58.Decode(splitAddress[0])
|
||||
decodedGateway := base58.Decode(splitAddress[1])
|
||||
|
||||
read_data, err := ioutil.ReadFile("dummy_file")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
payload := append(decodedDestination[:], append(decodedGateway[:], read_data[:]...)...)
|
||||
fmt.Printf("sending content of 'dummy file' over the mix network...\n")
|
||||
if err = conn.WriteMessage(websocket.BinaryMessage, payload); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sendConfirmationJSON := make(map[string]interface{})
|
||||
err = conn.ReadJSON(&sendConfirmationJSON)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if sendConfirmationJSON["type"].(string) != "send" {
|
||||
panic("invalid send confirmation")
|
||||
}
|
||||
|
||||
fmt.Printf("waiting to receive a message from the mix network...\n")
|
||||
_, receivedMessage, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("writing the file back to the disk!\n")
|
||||
ioutil.WriteFile("received_file", receivedMessage, 0644)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func getSelfAddress(conn *websocket.Conn) string {
|
||||
selfAddressRequest, err := json.Marshal(map[string]string{"type": "selfAddress"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = conn.WriteMessage(websocket.TextMessage, []byte(selfAddressRequest)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
responseJSON := make(map[string]interface{})
|
||||
err = conn.ReadJSON(&responseJSON)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return responseJSON["address"].(string)
|
||||
}
|
||||
|
||||
func main() {
|
||||
message := "Hello Nym!"
|
||||
|
||||
uri := "ws://localhost:1977"
|
||||
|
||||
conn, _, err := websocket.DefaultDialer.Dial(uri, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
selfAddress := getSelfAddress(conn)
|
||||
fmt.Printf("our address is: %v\n", selfAddress)
|
||||
sendRequest, err := json.Marshal(map[string]string{"type": "send", "recipient": selfAddress, "message": message})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("sending '%v' over the mix network...\n", message)
|
||||
if err = conn.WriteMessage(websocket.TextMessage, []byte(sendRequest)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sendConfirmationJSON := make(map[string]interface{})
|
||||
err = conn.ReadJSON(&sendConfirmationJSON)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if sendConfirmationJSON["type"].(string) != "send" {
|
||||
panic("invalid send confirmation")
|
||||
}
|
||||
|
||||
fmt.Printf("waiting to receive a message from the mix network...\n")
|
||||
_, receivedMessage, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("received %v from the mix network!\n", string(receivedMessage))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
dist/bundle.js
|
||||
node_modules
|
||||
@@ -0,0 +1,10 @@
|
||||
### Prerequisites
|
||||
|
||||
* Reasonably up to date Node + `npm`
|
||||
|
||||
### Running it
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start # starts a webserver on port 8888
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "nym-client-websocket-demo",
|
||||
"version": "0.7.0",
|
||||
"description": "Connect to a local Nym client, send and retrieve from the Nym network. ",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"start": "webpack-dev-server"
|
||||
},
|
||||
"keywords": [
|
||||
"nym",
|
||||
"anonymity",
|
||||
"mixnet",
|
||||
"client",
|
||||
"rust",
|
||||
"websockets"
|
||||
],
|
||||
"author": "Dave Hrycyszyn",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^9.8.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"html-webpack-plugin": "^4.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nym Client Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<label for="fname">Text to send: </label><input type="text" id="sendtext" name="sendtext" value="Hello mixnet!">
|
||||
<button id="send-button">Send</button>
|
||||
|
||||
<p>Send messages to the mixnet using the "send" button.</p>
|
||||
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
|
||||
messages show in green.</p>
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
<span id="output"></div>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,99 @@
|
||||
var ourAddress;
|
||||
|
||||
async function main() {
|
||||
var port = '1977' // client websocket listens on 1977 by default, change if yours is different
|
||||
var localClientUrl = "ws://127.0.0.1:" + port;
|
||||
|
||||
// Set up and handle websocket connection to our desktop client.
|
||||
var connection = await connectWebsocket(localClientUrl).then(function (c) {
|
||||
return c;
|
||||
}).catch(function (err) {
|
||||
display("Websocket connection error. Is the client running with <pre>--connection-type WebSocket</pre> on port " + port + "?");
|
||||
})
|
||||
connection.onmessage = function (e) {
|
||||
handleResponse(e);
|
||||
};
|
||||
|
||||
sendSelfAddressRequest(connection);
|
||||
|
||||
// Set up the send button
|
||||
const sendButton = document.querySelector('#send-button');
|
||||
sendButton.onclick = function () {
|
||||
sendMessageToMixnet(connection);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any messages that come back down the websocket.
|
||||
function handleResponse(resp) {
|
||||
// hacky workaround for receiving pushed 'text' messages,
|
||||
// basically we can either receive proper server responses, i.e. 'error', 'send', 'selfAddress'
|
||||
// or actual messages, without any framing, so they do not have 'type' field
|
||||
try {
|
||||
let response = JSON.parse(resp.data);
|
||||
if (response.type == "error") {
|
||||
displayJsonResponse("Server responded with error: " + response.message);
|
||||
} else if (response.type == "selfAddress") {
|
||||
displayJsonResponse(response);
|
||||
ourAddress = response.address;
|
||||
display("Our address is: " + ourAddress + ", we will now send messages to ourself.");
|
||||
}
|
||||
} catch (_) {
|
||||
displayJsonResponse(resp.data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Send a message to the mixnet.
|
||||
function sendMessageToMixnet(connection) {
|
||||
var sendText = document.getElementById("sendtext").value;
|
||||
var message = {
|
||||
type: "send",
|
||||
message: sendText,
|
||||
recipient: ourAddress
|
||||
}
|
||||
|
||||
displayJsonSend(message);
|
||||
connection.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
// Send a message to the mixnet client, asking what our own address is.
|
||||
// In this simplistic demo, we'll just use our own address to send ourselves messages.
|
||||
//
|
||||
// In a real application, you might want to ensure that somebody else got your
|
||||
// address so that they could send messages to you.
|
||||
function sendSelfAddressRequest(connection) {
|
||||
var selfAddress = {
|
||||
type: "selfAddress"
|
||||
}
|
||||
displayJsonSend(selfAddress);
|
||||
connection.send(JSON.stringify(selfAddress));
|
||||
}
|
||||
|
||||
function display(message) {
|
||||
document.getElementById("output").innerHTML += "<p>" + message + "</p >";
|
||||
}
|
||||
|
||||
function displayJsonSend(message) {
|
||||
document.getElementById("output").innerHTML += "<p style='color: blue;'>sent >>> " + JSON.stringify(message) + "</p >";
|
||||
}
|
||||
|
||||
function displayJsonResponse(message) {
|
||||
document.getElementById("output").innerHTML += "<p style='color: green;'>received <<<" + JSON.stringify(message) + "</p >";
|
||||
}
|
||||
|
||||
// Connect to a websocket.
|
||||
function connectWebsocket(url) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var server = new WebSocket(url);
|
||||
server.onopen = function () {
|
||||
resolve(server);
|
||||
};
|
||||
server.onerror = function (err) {
|
||||
reject(err);
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Start it!
|
||||
main();
|
||||
@@ -0,0 +1,25 @@
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
// path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
hash: true,
|
||||
template: './src/index.html',
|
||||
filename: '.././dist/index.html' //relative to root of the application
|
||||
})
|
||||
],
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'dist'),
|
||||
compress: true,
|
||||
port: 8888
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
base58 = "*"
|
||||
websockets = "*"
|
||||
asyncio = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.8"
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f72ba1bebddbc91273647a33428eb911071d256cf3fc20e04f51060a7610f293"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.8"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"asyncio": {
|
||||
"hashes": [
|
||||
"sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41",
|
||||
"sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de",
|
||||
"sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c",
|
||||
"sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.4.3"
|
||||
},
|
||||
"base58": {
|
||||
"hashes": [
|
||||
"sha256:4c7f5687da771b519cf86b3236250e7c3543368c576404c9fe2d992a287666e0",
|
||||
"sha256:c83584a8b917dc52dd634307137f2ad2721a9efb4f1de32fc7eaaaf87844177e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"websockets": {
|
||||
"hashes": [
|
||||
"sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5",
|
||||
"sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5",
|
||||
"sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308",
|
||||
"sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb",
|
||||
"sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a",
|
||||
"sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c",
|
||||
"sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170",
|
||||
"sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422",
|
||||
"sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8",
|
||||
"sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485",
|
||||
"sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f",
|
||||
"sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8",
|
||||
"sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc",
|
||||
"sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779",
|
||||
"sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989",
|
||||
"sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1",
|
||||
"sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092",
|
||||
"sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824",
|
||||
"sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d",
|
||||
"sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55",
|
||||
"sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36",
|
||||
"sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.1"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis efficitur neque. Quisque aliquet vulputate ante, eget vehicula odio feugiat ac. Nulla ut mattis magna. Aenean tincidunt quis nulla eget eleifend. Cras in pretium sem. Nunc lorem metus, blandit sit amet egestas ut, feugiat quis tellus. Aenean tristique, enim a tincidunt condimentum, eros est blandit nunc, id viverra metus erat at nulla. Vivamus at tellus sodales, feugiat odio vel, laoreet neque. Vivamus posuere nulla ac sodales bibendum.
|
||||
|
||||
Vestibulum pulvinar nisi non ultricies egestas. Integer finibus ultrices justo vitae suscipit. Etiam interdum eu justo vel interdum. Morbi sagittis ac nisl quis consequat. Mauris dapibus ut risus ac facilisis. Pellentesque non tortor feugiat, consectetur arcu vel, ullamcorper sapien. Proin sodales purus non orci bibendum, sit amet ultrices justo ullamcorper. Nullam ac risus ac justo ultricies efficitur auctor nec arcu. Etiam sed finibus felis. Suspendisse potenti. Phasellus malesuada velit ac ullamcorper egestas. Sed elementum diam ut est gravida ultricies.
|
||||
|
||||
Pellentesque sed metus massa. Cras imperdiet lacus sit amet dolor aliquam, luctus posuere justo hendrerit. Morbi augue ex, gravida a metus sed, scelerisque euismod lacus. Nam consequat sapien ac pellentesque sagittis. Morbi a ultrices massa, vel aliquet ex. Maecenas ac sem diam. Nunc sed erat et ipsum volutpat auctor. Etiam elit felis, commodo vitae ipsum ac, fermentum lobortis arcu. Aliquam eu tempus enim. Curabitur vulputate imperdiet aliquam. Morbi iaculis rhoncus risus at malesuada. Donec accumsan feugiat ligula ut facilisis. Nunc porttitor sit amet est eget malesuada. Sed sed consectetur augue, non dapibus orci. Mauris aliquam pellentesque quam, sit amet pellentesque velit cursus vitae. Morbi sit amet molestie risus.
|
||||
|
||||
Nam gravida non ligula a egestas. Fusce sodales, purus id rhoncus mattis, purus est vehicula urna, vel finibus augue velit et est. Donec dictum erat eleifend lobortis iaculis. Praesent id venenatis ante. Donec feugiat, ipsum eget porttitor pulvinar, nisl odio posuere lorem, ut placerat elit nulla a ligula. Suspendisse nec nibh tincidunt, sollicitudin mi a, volutpat ligula. In maximus quam lacus, eget semper dolor sagittis sit amet.
|
||||
|
||||
In vitae hendrerit est, quis facilisis dui. In eu ante enim. Nullam hendrerit odio sit amet odio tincidunt eleifend. Aliquam erat volutpat. Curabitur commodo, purus pharetra lobortis rhoncus, tortor massa imperdiet nisl, vel dignissim tortor sem at orci. Aliquam maximus lobortis lacus, eu porttitor purus dapibus ut. Praesent at dapibus felis, efficitur blandit tortor. In hac habitasse platea dictumst. Aenean ultrices, nisl a pretium sagittis, tellus sapien mollis erat, eu consectetur erat mauris sed libero. Duis feugiat dapibus mi, vel ornare velit vehicula mattis. Ut suscipit pharetra leo et sollicitudin.
|
||||
@@ -0,0 +1,38 @@
|
||||
import asyncio
|
||||
import base58
|
||||
import json
|
||||
import websockets
|
||||
|
||||
self_address_request = json.dumps({
|
||||
"type": "selfAddress"
|
||||
})
|
||||
|
||||
|
||||
async def send_file():
|
||||
uri = "ws://localhost:1977"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
await websocket.send(self_address_request)
|
||||
self_address = json.loads(await websocket.recv())
|
||||
print("our address is: {}".format(self_address["address"]))
|
||||
# we receive our address in string format of OUR_PUB_KEY @ OUR_GATE_PUB_KEY
|
||||
# both keys are 32 bytes and we need to encode them as binary without the '@' sign
|
||||
split_address = self_address["address"].split("@")
|
||||
bin_payload = bytearray(base58.b58decode(split_address[0]))
|
||||
bin_payload += base58.b58decode(split_address[1])
|
||||
|
||||
with open("dummy_file", "rb") as input_file:
|
||||
read_data = input_file.read()
|
||||
bin_payload += read_data
|
||||
|
||||
print("sending content of 'dummy_file' over the mix network...")
|
||||
await websocket.send(bin_payload)
|
||||
msg_send_confirmation = json.loads(await websocket.recv())
|
||||
assert msg_send_confirmation["type"], "send"
|
||||
|
||||
print("waiting to receive the 'dummy_file' from the mix network...")
|
||||
received_data = await websocket.recv()
|
||||
with open("received_file", "wb") as output_file:
|
||||
print("writing the file back to the disk!")
|
||||
output_file.write(received_data)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(send_file())
|
||||
@@ -0,0 +1,34 @@
|
||||
import asyncio
|
||||
import json
|
||||
import websockets
|
||||
|
||||
self_address_request = json.dumps({
|
||||
"type": "selfAddress"
|
||||
})
|
||||
|
||||
|
||||
async def send_text():
|
||||
message = "Hello Nym!"
|
||||
|
||||
uri = "ws://localhost:1977"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
await websocket.send(self_address_request)
|
||||
self_address = json.loads(await websocket.recv())
|
||||
print("our address is: {}".format(self_address["address"]))
|
||||
|
||||
text_send = json.dumps({
|
||||
"type": "send",
|
||||
"message": message,
|
||||
"recipient": self_address["address"]
|
||||
})
|
||||
|
||||
print("sending '{}' over the mix network...".format(message))
|
||||
await websocket.send(text_send)
|
||||
msg_send_confirmation = json.loads(await websocket.recv())
|
||||
assert msg_send_confirmation["type"], "send"
|
||||
|
||||
print("waiting to receive a message from the mix network...")
|
||||
received_message = await websocket.recv()
|
||||
print("received '{}' from the mix network".format(received_message))
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(send_text())
|
||||
@@ -0,0 +1,57 @@
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_client::websocket::{BinaryClientRequest, ClientRequest, ServerResponse};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use std::convert::TryFrom;
|
||||
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let uri = "ws://localhost:1977";
|
||||
let (mut ws_stream, _) = connect_async(uri).await.unwrap();
|
||||
|
||||
let self_address_request = ClientRequest::SelfAddress;
|
||||
ws_stream.send(self_address_request.into()).await.unwrap();
|
||||
|
||||
let raw_response = ws_stream.next().await.unwrap().unwrap();
|
||||
// what we received now is just a json, but we know it's exact format
|
||||
// so might as well use that
|
||||
let response = match raw_response {
|
||||
Message::Text(txt_msg) => ServerResponse::try_from(txt_msg).unwrap(),
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
};
|
||||
|
||||
let recipient = match response {
|
||||
ServerResponse::SelfAddress { address } => Recipient::try_from_string(address).unwrap(),
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
};
|
||||
println!("our full address is: {}", recipient.to_string());
|
||||
|
||||
let read_data = std::fs::read("examples/dummy_file").unwrap();
|
||||
|
||||
let send_request = BinaryClientRequest::Send {
|
||||
recipient,
|
||||
data: read_data,
|
||||
};
|
||||
|
||||
println!("sending content of 'dummy_file' over the mix network...");
|
||||
ws_stream.send(send_request.into()).await.unwrap();
|
||||
|
||||
let raw_send_confirmation = ws_stream.next().await.unwrap().unwrap();
|
||||
let _send_confirmation = match raw_send_confirmation {
|
||||
Message::Text(txt_msg) => match ServerResponse::try_from(txt_msg).unwrap() {
|
||||
ServerResponse::Send => (),
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
},
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
};
|
||||
|
||||
println!("waiting to receive the 'dummy_file' from the mix network...");
|
||||
let raw_message = ws_stream.next().await.unwrap().unwrap();
|
||||
let message = match raw_message {
|
||||
Message::Binary(bin_payload) => bin_payload,
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
};
|
||||
|
||||
println!("writing the file back to the disk!");
|
||||
std::fs::write("examples/received_file", message).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_client::websocket::{ClientRequest, ServerResponse};
|
||||
use std::convert::TryFrom;
|
||||
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let message = "Hello Nym!".to_string();
|
||||
|
||||
let uri = "ws://localhost:1977";
|
||||
let (mut ws_stream, _) = connect_async(uri).await.unwrap();
|
||||
|
||||
let self_address_request = ClientRequest::SelfAddress;
|
||||
ws_stream.send(self_address_request.into()).await.unwrap();
|
||||
|
||||
let raw_response = ws_stream.next().await.unwrap().unwrap();
|
||||
// what we received now is just a json, but we know it's exact format
|
||||
// so might as well use that
|
||||
let response = match raw_response {
|
||||
Message::Text(txt_msg) => ServerResponse::try_from(txt_msg).unwrap(),
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
};
|
||||
|
||||
let self_address = match response {
|
||||
ServerResponse::SelfAddress { address } => address,
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
};
|
||||
println!("our address is: {}", self_address.clone());
|
||||
|
||||
let send_request = ClientRequest::Send {
|
||||
message: message.clone(),
|
||||
recipient: self_address,
|
||||
};
|
||||
println!("sending {:?} over the mix network...", message);
|
||||
ws_stream.send(send_request.into()).await.unwrap();
|
||||
|
||||
let raw_send_confirmation = ws_stream.next().await.unwrap().unwrap();
|
||||
let _send_confirmation = match raw_send_confirmation {
|
||||
Message::Text(txt_msg) => match ServerResponse::try_from(txt_msg).unwrap() {
|
||||
ServerResponse::Send => (),
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
},
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
};
|
||||
|
||||
println!("waiting to receive a message from the mix network...");
|
||||
let raw_message = ws_stream.next().await.unwrap().unwrap();
|
||||
let message = match raw_message {
|
||||
Message::Text(txt_msg) => txt_msg,
|
||||
_ => panic!("received an unexpected response type!"),
|
||||
};
|
||||
|
||||
println!("received {:?} from the mix network!", message);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// The file has been placed there by the build script.
|
||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::mix_traffic::{MixMessage, MixMessageSender};
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::utils::{encapsulation, poisson};
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time;
|
||||
use topology::NymTopology;
|
||||
|
||||
pub(crate) struct LoopCoverTrafficStream<T: NymTopology> {
|
||||
average_packet_delay: Duration,
|
||||
average_cover_message_sending_delay: Duration,
|
||||
next_delay: time::Delay,
|
||||
mix_tx: MixMessageSender,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
}
|
||||
|
||||
impl<T: NymTopology> Stream for LoopCoverTrafficStream<T> {
|
||||
// Item is only used to indicate we should create a new message rather than actual cover message
|
||||
// reason being to not introduce unnecessary complexity by having to keep state of topology
|
||||
// mutex when trying to acquire it. So right now the Stream trait serves as a glorified timer.
|
||||
// Perhaps this should be changed in the future.
|
||||
type Item = ();
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// it is not yet time to return a message
|
||||
if Pin::new(&mut self.next_delay).poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = poisson::sample(self.average_cover_message_sending_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.reset(next);
|
||||
|
||||
Poll::Ready(Some(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + NymTopology> LoopCoverTrafficStream<T> {
|
||||
pub(crate) fn new(
|
||||
mix_tx: MixMessageSender,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
average_cover_message_sending_delay: time::Duration,
|
||||
average_packet_delay: time::Duration,
|
||||
) -> Self {
|
||||
LoopCoverTrafficStream {
|
||||
average_packet_delay,
|
||||
average_cover_message_sending_delay,
|
||||
next_delay: time::delay_for(Default::default()),
|
||||
mix_tx,
|
||||
our_full_destination,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_new_message(&mut self) {
|
||||
trace!("next cover message!");
|
||||
let route = match self
|
||||
.topology_access
|
||||
.random_route_to_gateway(&self.our_full_destination.gateway())
|
||||
.await
|
||||
{
|
||||
None => {
|
||||
warn!("No valid topology detected - won't send any loop cover message this time");
|
||||
return;
|
||||
}
|
||||
Some(route) => route,
|
||||
};
|
||||
|
||||
let cover_message = match encapsulation::loop_cover_message_route(
|
||||
self.our_full_destination.destination().clone(),
|
||||
route,
|
||||
self.average_packet_delay,
|
||||
) {
|
||||
Ok(message) => message,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Somehow we managed to create an invalid cover message - {:?}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
// - the receiver channel is closed
|
||||
// in either case there's no recovery and we can only panic
|
||||
self.mix_tx
|
||||
.unbounded_send(MixMessage::new(cover_message.0, cover_message.1))
|
||||
.unwrap();
|
||||
// JS: due to identical logical structure to OutQueueControl::on_message(), this is also
|
||||
// presumably required to prevent bugs in the future. Exact reason is still unknown to me.
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay =
|
||||
time::delay_for(poisson::sample(self.average_cover_message_sending_delay));
|
||||
|
||||
while let Some(_) = self.next().await {
|
||||
self.on_new_message().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
self.run().await;
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use futures::channel::mpsc;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
|
||||
pub(crate) type InputMessageSender = mpsc::UnboundedSender<InputMessage>;
|
||||
pub(crate) type InputMessageReceiver = mpsc::UnboundedReceiver<InputMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InputMessage {
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl InputMessage {
|
||||
pub(crate) fn new(recipient: Recipient, data: Vec<u8>) -> Self {
|
||||
InputMessage { recipient, data }
|
||||
}
|
||||
|
||||
// I'm open to suggestions on how to rename this.
|
||||
pub(crate) fn destruct(self) -> (Recipient, Vec<u8>) {
|
||||
(self.recipient, self.data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::GatewayClient;
|
||||
use log::*;
|
||||
use nymsphinx::SphinxPacket;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub(crate) struct MixMessage(SocketAddr, SphinxPacket);
|
||||
pub(crate) type MixMessageSender = mpsc::UnboundedSender<MixMessage>;
|
||||
pub(crate) type MixMessageReceiver = mpsc::UnboundedReceiver<MixMessage>;
|
||||
|
||||
impl MixMessage {
|
||||
pub(crate) fn new(address: SocketAddr, packet: SphinxPacket) -> Self {
|
||||
MixMessage(address, packet)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MixTrafficController<'a> {
|
||||
// TODO: most likely to be replaced by some higher level construct as
|
||||
// later on gateway_client will need to be accessible by other entities
|
||||
gateway_client: GatewayClient<'a, url::Url>,
|
||||
mix_rx: MixMessageReceiver,
|
||||
}
|
||||
|
||||
impl<'a> MixTrafficController<'static> {
|
||||
pub(crate) fn new(
|
||||
mix_rx: MixMessageReceiver,
|
||||
gateway_client: GatewayClient<'a, url::Url>,
|
||||
) -> MixTrafficController<'a> {
|
||||
MixTrafficController {
|
||||
gateway_client,
|
||||
mix_rx,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, mix_message: MixMessage) {
|
||||
debug!("Got a mix_message for {:?}", mix_message.0);
|
||||
match self
|
||||
.gateway_client
|
||||
.send_sphinx_packet(mix_message.0, mix_message.1)
|
||||
.await
|
||||
{
|
||||
Err(e) => error!("Failed to send sphinx packet to the gateway! - {:?}", e),
|
||||
Ok(_) => trace!("We *might* have managed to forward sphinx packet to the gateway!"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
while let Some(mix_message) = self.mix_rx.next().await {
|
||||
self.on_message(mix_message).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
self.run().await;
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
|
||||
use crate::client::mix_traffic::{MixMessageReceiver, MixMessageSender, MixTrafficController};
|
||||
use crate::client::received_buffer::{
|
||||
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
|
||||
};
|
||||
use crate::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use crate::config::{Config, SocketType};
|
||||
use crate::websocket;
|
||||
use crypto::identity::MixIdentityKeyPair;
|
||||
use directory_client::presence;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{GatewayClient, SphinxPacketReceiver, SphinxPacketSender};
|
||||
use gateway_requests::auth_token::AuthToken;
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::chunking::split_and_prepare_payloads;
|
||||
use nymsphinx::NodeAddressBytes;
|
||||
use received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
|
||||
use tokio::runtime::Runtime;
|
||||
use topology::NymTopology;
|
||||
|
||||
mod cover_traffic_stream;
|
||||
pub(crate) mod inbound_messages;
|
||||
mod mix_traffic;
|
||||
mod real_traffic_stream;
|
||||
pub(crate) mod received_buffer;
|
||||
pub(crate) mod topology_control;
|
||||
|
||||
pub struct NymClient {
|
||||
config: Config,
|
||||
runtime: Runtime,
|
||||
identity_keypair: MixIdentityKeyPair,
|
||||
|
||||
// to be used by "send" function or socket, etc
|
||||
input_tx: Option<InputMessageSender>,
|
||||
|
||||
// to be used by "receive" function or socket, etc
|
||||
receive_tx: Option<ReconstructedMessagesReceiver>,
|
||||
}
|
||||
|
||||
impl NymClient {
|
||||
pub fn new(config: Config, identity_keypair: MixIdentityKeyPair) -> Self {
|
||||
NymClient {
|
||||
runtime: Runtime::new().unwrap(),
|
||||
config,
|
||||
identity_keypair,
|
||||
input_tx: None,
|
||||
receive_tx: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mix_recipient(&self) -> Recipient {
|
||||
Recipient::new(
|
||||
self.identity_keypair.public_key.derive_address(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeAddressBytes::try_from_base58_string(self.config.get_gateway_id()).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
// future constantly pumping loop cover traffic at some specified average rate
|
||||
// the pumped traffic goes to the MixTrafficController
|
||||
fn start_cover_traffic_stream<T: 'static + NymTopology>(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
mix_tx: MixMessageSender,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor which HAS TO be called within context of a tokio runtime
|
||||
self.runtime
|
||||
.enter(|| {
|
||||
LoopCoverTrafficStream::new(
|
||||
mix_tx,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
self.config.get_loop_cover_traffic_average_delay(),
|
||||
self.config.get_average_packet_delay(),
|
||||
)
|
||||
})
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_real_traffic_stream<T: 'static + NymTopology>(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
mix_tx: MixMessageSender,
|
||||
input_rx: InputMessageReceiver,
|
||||
) {
|
||||
info!("Starting real traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor which HAS TO be called within context of a tokio runtime
|
||||
self.runtime
|
||||
.enter(|| {
|
||||
real_traffic_stream::OutQueueControl::new(
|
||||
mix_tx,
|
||||
input_rx,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
self.config.get_average_packet_delay(),
|
||||
self.config.get_message_sending_average_delay(),
|
||||
)
|
||||
})
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
// required so that other components would be able to use them (say the websocket)
|
||||
fn start_received_messages_buffer_controller(
|
||||
&self,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
sphinx_receiver: SphinxPacketReceiver,
|
||||
) {
|
||||
info!("Starting received messages buffer controller...");
|
||||
ReceivedMessagesBufferController::new(query_receiver, sphinx_receiver)
|
||||
.start(self.runtime.handle())
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
sphinx_packet_sender: SphinxPacketSender,
|
||||
gateway_address: url::Url,
|
||||
) -> GatewayClient<'static, url::Url> {
|
||||
let auth_token = self
|
||||
.config
|
||||
.get_gateway_auth_token()
|
||||
.map(|str_token| AuthToken::try_from_base58_string(str_token).ok())
|
||||
.unwrap_or(None);
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.as_mix_recipient().destination(),
|
||||
auth_token,
|
||||
sphinx_packet_sender,
|
||||
self.config.get_gateway_response_timeout(),
|
||||
);
|
||||
|
||||
let auth_token = self.runtime.block_on(async {
|
||||
gateway_client
|
||||
.establish_connection()
|
||||
.await
|
||||
.expect("could not establish initial connection with the gateway");
|
||||
gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.expect("could not perform initial authentication with the gateway")
|
||||
});
|
||||
|
||||
// TODO: if we didn't have an auth_token initially, save it to config or something?
|
||||
info!(
|
||||
"Performed initial authentication. Auth token is {:?}",
|
||||
auth_token.to_base58_string()
|
||||
);
|
||||
|
||||
gateway_client
|
||||
}
|
||||
|
||||
// TODO: this information should be just put in the config on init...
|
||||
async fn get_gateway_address<T: NymTopology>(
|
||||
gateway_id: String,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
) -> url::Url {
|
||||
// we already have our gateway written in the config
|
||||
let gateway_address = topology_accessor
|
||||
.get_gateway_socket_url(&gateway_id)
|
||||
.await
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Could not find gateway with id {:?}.\
|
||||
It does not seem to be present in the current network topology.\
|
||||
Are you sure it is still online?\
|
||||
Perhaps try to run `nym-client init` again to obtain a new gateway",
|
||||
gateway_id
|
||||
)
|
||||
});
|
||||
|
||||
url::Url::parse(&gateway_address).expect("provided gateway address is invalid!")
|
||||
}
|
||||
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// the current global view of topology
|
||||
fn start_topology_refresher<T: 'static + NymTopology>(
|
||||
&mut self,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
) {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_directory_server(),
|
||||
self.config.get_topology_refresh_rate(),
|
||||
);
|
||||
let mut topology_refresher =
|
||||
TopologyRefresher::new(topology_refresher_config, topology_accessor);
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
info!(
|
||||
"Obtaining initial network topology from {}",
|
||||
self.config.get_directory_server()
|
||||
);
|
||||
self.runtime.block_on(topology_refresher.refresh());
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !self
|
||||
.runtime
|
||||
.block_on(topology_refresher.is_topology_routable())
|
||||
{
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
- check if enough nodes and a gateway are online"
|
||||
);
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
|
||||
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(
|
||||
&mut self,
|
||||
mix_rx: MixMessageReceiver,
|
||||
gateway_client: GatewayClient<'static, url::Url>,
|
||||
) {
|
||||
info!("Starting mix traffic controller...");
|
||||
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_websocket_listener<T: 'static + NymTopology>(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
msg_input: InputMessageSender,
|
||||
) {
|
||||
info!("Starting websocket listener...");
|
||||
|
||||
let websocket_handler = websocket::Handler::new(
|
||||
msg_input,
|
||||
buffer_requester,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
);
|
||||
|
||||
websocket::Listener::new(self.config.get_listening_port())
|
||||
.start(self.runtime.handle(), websocket_handler);
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub fn send_message(&mut self, recipient: Recipient, message: Vec<u8>) {
|
||||
let split_message = split_and_prepare_payloads(&message);
|
||||
debug!(
|
||||
"Splitting message into {:?} fragments!",
|
||||
split_message.len()
|
||||
);
|
||||
for message_fragment in split_message {
|
||||
let input_msg = InputMessage::new(recipient.clone(), message_fragment);
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
.expect("start method was not called before!")
|
||||
.unbounded_send(input_msg)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
/// Note: it waits for the first occurrence of messages being sent to ourselves. If you expect multiple
|
||||
/// messages, you might have to call this function repeatedly.
|
||||
pub async fn wait_for_messages(&mut self) -> Vec<Vec<u8>> {
|
||||
use futures::StreamExt;
|
||||
|
||||
self.receive_tx
|
||||
.as_mut()
|
||||
.expect("start method was not called before!")
|
||||
.next()
|
||||
.await
|
||||
.expect("buffer controller seems to have somehow died!")
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub fn run_forever(&mut self) {
|
||||
self.start();
|
||||
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
|
||||
error!(
|
||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
println!(
|
||||
"Received SIGINT - the client will terminate now (threads are not YET nicely stopped)"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
// rather than creating them here, so say for example the buffer controller would create the request channels
|
||||
// and would allow anyone to clone the sender channel
|
||||
|
||||
// sphinx_message_sender is the transmitter for any component generating sphinx packets that are to be sent to the mixnet
|
||||
// they are used by cover traffic stream and real traffic stream
|
||||
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
|
||||
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
|
||||
|
||||
// unwrapped_sphinx_sender is the transmitter of [unwrapped] sphinx messages received from the gateway
|
||||
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
|
||||
let (unwrapped_sphinx_sender, unwrapped_sphinx_receiver) = mpsc::unbounded();
|
||||
|
||||
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
|
||||
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
|
||||
|
||||
// channels responsible for controlling real messages
|
||||
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
|
||||
|
||||
// TODO: when we switch to our graph topology, we need to remember to change 'presence::Topology' type
|
||||
let shared_topology_accessor = TopologyAccessor::<presence::Topology>::new();
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone());
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
unwrapped_sphinx_receiver,
|
||||
);
|
||||
|
||||
let gateway_url = self.runtime.block_on(Self::get_gateway_address(
|
||||
self.config.get_gateway_id(),
|
||||
shared_topology_accessor.clone(),
|
||||
));
|
||||
let gateway_client = self.start_gateway_client(unwrapped_sphinx_sender, gateway_url);
|
||||
|
||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
||||
self.start_cover_traffic_stream(
|
||||
shared_topology_accessor.clone(),
|
||||
sphinx_message_sender.clone(),
|
||||
);
|
||||
self.start_real_traffic_stream(
|
||||
shared_topology_accessor.clone(),
|
||||
sphinx_message_sender,
|
||||
input_receiver,
|
||||
);
|
||||
|
||||
match self.config.get_socket_type() {
|
||||
SocketType::WebSocket => self.start_websocket_listener(
|
||||
shared_topology_accessor,
|
||||
received_buffer_request_sender,
|
||||
input_sender,
|
||||
),
|
||||
SocketType::None => {
|
||||
// if we did not start the socket, it means we're running (supposedly) in the native mode
|
||||
// and hence we should announce 'ourselves' to the buffer
|
||||
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
|
||||
|
||||
// tell the buffer to start sending stuff to us
|
||||
received_buffer_request_sender
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
.expect("the buffer request failed!");
|
||||
|
||||
self.receive_tx = Some(reconstructed_receiver);
|
||||
self.input_tx = Some(input_sender);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Client startup finished!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::inbound_messages::InputMessage;
|
||||
use crate::client::mix_traffic::MixMessage;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use futures::channel::mpsc;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use log::{error, info, trace, warn};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::utils::{encapsulation, poisson};
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time;
|
||||
use topology::NymTopology;
|
||||
|
||||
pub(crate) struct OutQueueControl<T: NymTopology> {
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
next_delay: time::Delay,
|
||||
mix_tx: mpsc::UnboundedSender<MixMessage>,
|
||||
input_rx: mpsc::UnboundedReceiver<InputMessage>,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
}
|
||||
|
||||
pub(crate) enum StreamMessage {
|
||||
Cover,
|
||||
Real(InputMessage),
|
||||
}
|
||||
|
||||
impl<T: NymTopology> Stream for OutQueueControl<T> {
|
||||
type Item = StreamMessage;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// it is not yet time to return a message
|
||||
if Pin::new(&mut self.next_delay).poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = poisson::sample(self.average_message_sending_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.reset(next);
|
||||
|
||||
// decide what kind of message to send
|
||||
match Pin::new(&mut self.input_rx).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there's an actual message - return it
|
||||
Poll::Ready(Some(real_message)) => Poll::Ready(Some(StreamMessage::Real(real_message))),
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + NymTopology> OutQueueControl<T> {
|
||||
pub(crate) fn new(
|
||||
mix_tx: mpsc::UnboundedSender<MixMessage>,
|
||||
input_rx: mpsc::UnboundedReceiver<InputMessage>,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
average_packet_delay,
|
||||
average_message_sending_delay,
|
||||
next_delay: time::delay_for(Default::default()),
|
||||
mix_tx,
|
||||
input_rx,
|
||||
our_full_destination,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_route(&self, other_recipient: Option<&Recipient>) -> Option<Vec<nymsphinx::Node>> {
|
||||
match other_recipient {
|
||||
// we are sending to ourselves
|
||||
None => {
|
||||
self.topology_access
|
||||
.random_route_to_gateway(&self.our_full_destination.gateway())
|
||||
.await
|
||||
}
|
||||
Some(other_recipient) => {
|
||||
self.topology_access
|
||||
.random_route_to_gateway(&other_recipient.gateway())
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, next_message: StreamMessage) {
|
||||
trace!("created new message");
|
||||
|
||||
let next_packet = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
let route = self.get_route(None).await;
|
||||
if route.is_none() {
|
||||
warn!("No valid topology detected - won't send any real or loop message this time");
|
||||
return;
|
||||
}
|
||||
let route = route.unwrap();
|
||||
encapsulation::loop_cover_message_route(
|
||||
self.our_full_destination.destination().clone(),
|
||||
route,
|
||||
self.average_packet_delay,
|
||||
)
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
let (recipient, data) = real_message.destruct();
|
||||
let route = self.get_route(Some(&recipient)).await;
|
||||
if route.is_none() {
|
||||
warn!("No valid topology detected - won't send any real or loop message this time");
|
||||
return;
|
||||
}
|
||||
let route = route.unwrap();
|
||||
encapsulation::encapsulate_message_route(
|
||||
recipient.destination(),
|
||||
data,
|
||||
route,
|
||||
self.average_packet_delay,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let next_packet = match next_packet {
|
||||
Ok(message) => message,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Somehow we managed to create an invalid traffic message - {:?}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
// - the receiver channel is closed
|
||||
// in either case there's no recovery and we can only panic
|
||||
self.mix_tx
|
||||
.unbounded_send(MixMessage::new(next_packet.0, next_packet.1))
|
||||
.unwrap();
|
||||
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
||||
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
|
||||
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
|
||||
// ready and hence was immediately re-scheduled causing other tasks to be starved;
|
||||
// yield makes it go back the scheduling queue regardless of its value availability
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
pub(crate) async fn run_out_queue_control(mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay = time::delay_for(poisson::sample(self.average_message_sending_delay));
|
||||
|
||||
info!("Starting out queue controller...");
|
||||
while let Some(next_message) = self.next().await {
|
||||
self.on_message(next_message).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move { self.run_out_queue_control().await })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::SphinxPacketReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
chunking::reconstruction::MessageReconstructor,
|
||||
utils::encapsulation::LOOP_COVER_MESSAGE_PAYLOAD,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
|
||||
// or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead"
|
||||
pub(crate) type ReceivedBufferRequestSender = mpsc::UnboundedSender<ReceivedBufferMessage>;
|
||||
pub(crate) type ReceivedBufferRequestReceiver = mpsc::UnboundedReceiver<ReceivedBufferMessage>;
|
||||
|
||||
// The channel set for the above
|
||||
pub(crate) type ReconstructedMessagesSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
|
||||
pub(crate) type ReconstructedMessagesReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
||||
|
||||
struct ReceivedMessagesBufferInner {
|
||||
messages: Vec<Vec<u8>>,
|
||||
message_reconstructor: MessageReconstructor,
|
||||
message_sender: Option<ReconstructedMessagesSender>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
// Note: you should NEVER create more than a single instance of this using 'new()'.
|
||||
// You should always use .clone() to create additional instances
|
||||
struct ReceivedMessagesBuffer {
|
||||
inner: Arc<Mutex<ReceivedMessagesBufferInner>>,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBuffer {
|
||||
fn new() -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
messages: Vec::new(),
|
||||
message_reconstructor: MessageReconstructor::new(),
|
||||
message_sender: None,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
async fn disconnect_sender(&mut self) {
|
||||
let mut guard = self.inner.lock().await;
|
||||
if guard.message_sender.is_none() {
|
||||
// in theory we could just ignore it, but that situation should have never happened
|
||||
// in the first place, so this way we at least know we have an important bug to fix
|
||||
panic!("trying to disconnect non-existent sender!")
|
||||
}
|
||||
guard.message_sender = None;
|
||||
}
|
||||
|
||||
async fn connect_sender(&mut self, sender: ReconstructedMessagesSender) {
|
||||
let mut guard = self.inner.lock().await;
|
||||
if guard.message_sender.is_some() {
|
||||
// in theory we could just ignore it, but that situation should have never happened
|
||||
// in the first place, so this way we at least know we have an important bug to fix
|
||||
panic!("trying overwrite an existing sender!")
|
||||
}
|
||||
|
||||
// while we're at it, also empty the buffer if we happened to receive anything while
|
||||
// no sender was connected
|
||||
let stored_messages = std::mem::replace(&mut guard.messages, Vec::new());
|
||||
if !stored_messages.is_empty() {
|
||||
if let Err(err) = sender.unbounded_send(stored_messages) {
|
||||
error!(
|
||||
"The sender channel we just received is already invalidated - {:?}",
|
||||
err
|
||||
);
|
||||
// put the values back to the buffer
|
||||
// the returned error has two fields: err: SendError and val: T,
|
||||
// where val is the value that was failed to get sent;
|
||||
// it's returned by the `into_inner` call
|
||||
guard.messages = err.into_inner();
|
||||
return;
|
||||
}
|
||||
}
|
||||
guard.message_sender = Some(sender);
|
||||
}
|
||||
|
||||
async fn add_reconstructed_messages(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
debug!("Adding {:?} new messages to the buffer!", msgs.len());
|
||||
trace!("Adding new messages to the buffer! {:?}", msgs);
|
||||
self.inner.lock().await.messages.extend(msgs)
|
||||
}
|
||||
|
||||
async fn add_new_message_fragments(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
debug!(
|
||||
"Adding {:?} new message fragments to the buffer!",
|
||||
msgs.len()
|
||||
);
|
||||
trace!("Adding new message fragments to the buffer! {:?}", msgs);
|
||||
|
||||
let mut completed_messages = Vec::new();
|
||||
let mut inner_guard = self.inner.lock().await;
|
||||
for msg_fragment in msgs {
|
||||
if msg_fragment == LOOP_COVER_MESSAGE_PAYLOAD {
|
||||
trace!("The message was a loop cover message! Skipping it");
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(reconstructed_message) =
|
||||
inner_guard.message_reconstructor.new_fragment(msg_fragment)
|
||||
{
|
||||
completed_messages.push(reconstructed_message);
|
||||
}
|
||||
}
|
||||
|
||||
if !completed_messages.is_empty() {
|
||||
if let Some(sender) = &inner_guard.message_sender {
|
||||
trace!("Sending reconstructed messages to announced sender");
|
||||
if let Err(err) = sender.unbounded_send(completed_messages) {
|
||||
warn!("The reconstructed message receiver went offline without explicit notification (relevant error: - {:?})", err);
|
||||
// make sure to drop the lock to not deadlock
|
||||
// (it is required by `add_reconstructed_messages`)
|
||||
inner_guard.message_sender = None;
|
||||
drop(inner_guard);
|
||||
self.add_reconstructed_messages(err.into_inner()).await;
|
||||
}
|
||||
} else {
|
||||
// make sure to drop the lock to not deadlock
|
||||
// (it is required by `add_reconstructed_messages`)
|
||||
drop(inner_guard);
|
||||
trace!("No sender available - buffering reconstructed messages");
|
||||
self.add_reconstructed_messages(completed_messages).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ReceivedBufferMessage {
|
||||
// Signals a websocket connection (or a native implementation) was established and we should stop buffering messages,
|
||||
// and instead send them directly to the received channel
|
||||
ReceiverAnnounce(ReconstructedMessagesSender),
|
||||
|
||||
// Explicit signal that Receiver connection will no longer accept messages
|
||||
ReceiverDisconnect,
|
||||
}
|
||||
|
||||
struct RequestReceiver {
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
}
|
||||
|
||||
impl RequestReceiver {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
) -> Self {
|
||||
RequestReceiver {
|
||||
received_buffer,
|
||||
query_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
while let Some(request) = self.query_receiver.next().await {
|
||||
match request {
|
||||
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
|
||||
self.received_buffer.connect_sender(sender).await;
|
||||
}
|
||||
ReceivedBufferMessage::ReceiverDisconnect => {
|
||||
self.received_buffer.disconnect_sender().await
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct FragmentedMessageReceiver {
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
sphinx_packet_receiver: SphinxPacketReceiver,
|
||||
}
|
||||
|
||||
impl FragmentedMessageReceiver {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
sphinx_packet_receiver: SphinxPacketReceiver,
|
||||
) -> Self {
|
||||
FragmentedMessageReceiver {
|
||||
received_buffer,
|
||||
sphinx_packet_receiver,
|
||||
}
|
||||
}
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
while let Some(new_messages) = self.sphinx_packet_receiver.next().await {
|
||||
self.received_buffer
|
||||
.add_new_message_fragments(new_messages)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ReceivedMessagesBufferController {
|
||||
fragmented_messsage_receiver: FragmentedMessageReceiver,
|
||||
request_receiver: RequestReceiver,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBufferController {
|
||||
pub(crate) fn new(
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
sphinx_packet_receiver: SphinxPacketReceiver,
|
||||
) -> Self {
|
||||
let received_buffer = ReceivedMessagesBuffer::new();
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
fragmented_messsage_receiver: FragmentedMessageReceiver::new(
|
||||
received_buffer.clone(),
|
||||
sphinx_packet_receiver,
|
||||
),
|
||||
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(self, handle: &Handle) {
|
||||
// TODO: should we do anything with JoinHandle(s) returned by start methods?
|
||||
self.fragmented_messsage_receiver.start(handle);
|
||||
self.request_receiver.start(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::built_info;
|
||||
use futures::lock::Mutex;
|
||||
use log::*;
|
||||
use nymsphinx::NodeAddressBytes;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use topology::{provider, NymTopology};
|
||||
|
||||
struct TopologyAccessorInner<T: NymTopology>(Option<T>);
|
||||
|
||||
impl<T: NymTopology> TopologyAccessorInner<T> {
|
||||
fn new() -> Self {
|
||||
TopologyAccessorInner(None)
|
||||
}
|
||||
|
||||
fn update(&mut self, new: Option<T>) {
|
||||
self.0 = new;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct TopologyAccessor<T: NymTopology> {
|
||||
// TODO: this requires some actual benchmarking to determine if obtaining mutex is not going
|
||||
// to cause some bottlenecking and whether perhaps RwLock would be better
|
||||
inner: Arc<Mutex<TopologyAccessorInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T: NymTopology> TopologyAccessor<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
TopologyAccessor {
|
||||
inner: Arc::new(Mutex::new(TopologyAccessorInner::new())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_global_topology(&mut self, new_topology: Option<T>) {
|
||||
self.inner.lock().await.update(new_topology);
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateway_socket_url(&self, id: &str) -> Option<String> {
|
||||
match &self.inner.lock().await.0 {
|
||||
None => None,
|
||||
Some(ref topology) => topology
|
||||
.gateways()
|
||||
.iter()
|
||||
.find(|gateway| gateway.pub_key == id)
|
||||
.map(|gateway| gateway.client_listener.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
// only used by the client at startup to get a slightly more reasonable error message
|
||||
// (currently displays as unused because healthchecker is disabled due to required changes)
|
||||
pub(crate) async fn is_routable(&self) -> bool {
|
||||
match &self.inner.lock().await.0 {
|
||||
None => false,
|
||||
Some(ref topology) => topology.can_construct_path_through(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_all_clients(&self) -> Option<Vec<provider::Client>> {
|
||||
// TODO: this will need to be modified to instead return pairs (provider, client)
|
||||
match &self.inner.lock().await.0 {
|
||||
None => None,
|
||||
Some(ref topology) => Some(
|
||||
topology
|
||||
.providers()
|
||||
.iter()
|
||||
.flat_map(|provider| provider.registered_clients.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn random_route_to_gateway(
|
||||
&self,
|
||||
gateway: &NodeAddressBytes,
|
||||
) -> Option<Vec<nymsphinx::Node>> {
|
||||
let b58_address = gateway.to_base58_string();
|
||||
let guard = self.inner.lock().await;
|
||||
let topology = guard.0.as_ref()?;
|
||||
|
||||
let gateway = topology
|
||||
.gateways()
|
||||
.iter()
|
||||
.cloned()
|
||||
.find(|gateway| gateway.pub_key == b58_address.clone())?;
|
||||
|
||||
topology.random_route_to_gateway(gateway.into()).ok()
|
||||
}
|
||||
|
||||
// // this is a rather temporary solution as each client will have an associated provider
|
||||
// // currently that is not implemented yet and there only exists one provider in the network
|
||||
// pub(crate) async fn random_route(&self) -> Option<Vec<nymsphinx::Node>> {
|
||||
// match &self.inner.lock().await.0 {
|
||||
// None => None,
|
||||
// Some(ref topology) => {
|
||||
// let mut gateways = topology.gateways();
|
||||
// if gateways.is_empty() {
|
||||
// return None;
|
||||
// }
|
||||
// // unwrap is fine here as we asserted there is at least single provider
|
||||
// let provider = gateways.pop().unwrap().into();
|
||||
// topology.random_route_to_gateway(provider).ok()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
pub(crate) struct TopologyRefresherConfig {
|
||||
directory_server: String,
|
||||
refresh_rate: time::Duration,
|
||||
}
|
||||
|
||||
impl TopologyRefresherConfig {
|
||||
pub(crate) fn new(directory_server: String, refresh_rate: time::Duration) -> Self {
|
||||
TopologyRefresherConfig {
|
||||
directory_server,
|
||||
refresh_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TopologyRefresher<T: NymTopology> {
|
||||
directory_server: String,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
refresh_rate: Duration,
|
||||
}
|
||||
|
||||
impl<T: 'static + NymTopology> TopologyRefresher<T> {
|
||||
pub(crate) fn new(
|
||||
cfg: TopologyRefresherConfig,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
) -> Self {
|
||||
TopologyRefresher {
|
||||
directory_server: cfg.directory_server,
|
||||
topology_accessor,
|
||||
refresh_rate: cfg.refresh_rate,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&self) -> T {
|
||||
// note: this call makes it necessary that `T::new()`does *not* have 'static lifetime
|
||||
let full_topology = T::new(self.directory_server.clone()).await;
|
||||
// just filter by version and assume the validators will remove all bad behaving
|
||||
// nodes with the staking
|
||||
full_topology.filter_system_version(built_info::PKG_VERSION)
|
||||
}
|
||||
|
||||
pub(crate) async fn refresh(&mut self) {
|
||||
trace!("Refreshing the topology");
|
||||
let new_topology = Some(self.get_current_compatible_topology().await);
|
||||
|
||||
self.topology_accessor
|
||||
.update_global_topology(new_topology)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn is_topology_routable(&self) -> bool {
|
||||
self.topology_accessor.is_routable().await
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
loop {
|
||||
tokio::time::delay_for(self.refresh_rate).await;
|
||||
self.refresh().await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::built_info;
|
||||
use crate::commands::override_config;
|
||||
use crate::config::persistence::pathfinder::ClientPathfinder;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::identity::MixIdentityKeyPair;
|
||||
use directory_client::presence::Topology;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::AuthToken;
|
||||
use nymsphinx::DestinationAddressBytes;
|
||||
use pemstore::pemstore::PemStore;
|
||||
use std::time::Duration;
|
||||
use topology::gateway::Node;
|
||||
use topology::NymTopology;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
App::new("init")
|
||||
.about("Initialise a Nym client. Do this first!")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to create config for.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(Arg::with_name("gateway")
|
||||
.long("gateway")
|
||||
.help("Id of the gateway we have preference to connect to. If left empty, a random gateway will be chosen.")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("directory")
|
||||
.long("directory")
|
||||
.help("Address of the directory server the client is getting topology from")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("disable-socket")
|
||||
.long("disable-socket")
|
||||
.help("Whether to not start the websocket")
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port for the socket (if applicable) to listen on in all subsequent runs")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("fastmode")
|
||||
.long("fastmode")
|
||||
.hidden(true) // this will prevent this flag from being displayed in `--help`
|
||||
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
|
||||
)
|
||||
}
|
||||
|
||||
async fn try_gateway_registration(
|
||||
gateways: Vec<Node>,
|
||||
our_address: DestinationAddressBytes,
|
||||
) -> Option<(String, AuthToken)> {
|
||||
// TODO: having to do something like this suggests that perhaps GatewayClient's constructor
|
||||
// could be improved
|
||||
let (sphinx_tx, _) = mpsc::unbounded();
|
||||
let timeout = Duration::from_millis(1500);
|
||||
for gateway in gateways {
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
url::Url::parse(&gateway.client_listener).unwrap(),
|
||||
our_address.clone(),
|
||||
None,
|
||||
sphinx_tx.clone(),
|
||||
timeout,
|
||||
);
|
||||
if gateway_client.establish_connection().await.is_ok() {
|
||||
if let Ok(token) = gateway_client.register().await {
|
||||
return Some((gateway.pub_key, token));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn choose_gateway(
|
||||
directory_server: String,
|
||||
our_address: DestinationAddressBytes,
|
||||
) -> (String, AuthToken) {
|
||||
// TODO: once we change to graph topology this here will need to be updated!
|
||||
let topology = Topology::new(directory_server.clone()).await;
|
||||
let version_filtered_topology = topology.filter_system_version(built_info::PKG_VERSION);
|
||||
// don't care about health of the networks as mixes can go up and down any time,
|
||||
// but DO care about gateways
|
||||
let gateways = version_filtered_topology.gateways();
|
||||
|
||||
// try to perform registration so that we wouldn't need to do it at startup
|
||||
// + at the same time we'll know if we can actually talk with that gateway
|
||||
let registration_result = try_gateway_registration(gateways, our_address).await;
|
||||
match registration_result {
|
||||
None => {
|
||||
// while technically there's no issue client-side, it will be impossible to execute
|
||||
// `nym-client run` as no gateway is available so it might be best to not finalize
|
||||
// the init and rely on users trying to init another time?
|
||||
panic!(
|
||||
"Currently there are no valid gateways available on the network ({}). \
|
||||
Please try to run `init` again at later time or change your directory server",
|
||||
directory_server
|
||||
)
|
||||
}
|
||||
Some((gateway_id, auth_token)) => (gateway_id, auth_token),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = matches.value_of("id").unwrap(); // required for now
|
||||
let mut config = crate::config::Config::new(id);
|
||||
|
||||
config = override_config(config, matches);
|
||||
if matches.is_present("fastmode") {
|
||||
config = config.set_high_default_traffic_volume();
|
||||
}
|
||||
|
||||
let mix_identity_keys = MixIdentityKeyPair::new();
|
||||
|
||||
// if there is no gateway chosen, get a random-ish one from the topology
|
||||
if config.get_gateway_id().is_empty() {
|
||||
let our_address = mix_identity_keys.public_key().derive_address();
|
||||
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let (gateway_id, auth_token) =
|
||||
rt.block_on(choose_gateway(config.get_directory_server(), our_address));
|
||||
|
||||
// TODO: this isn't really a gateway, but gateway, yet another change to make
|
||||
config = config
|
||||
.with_gateway_id(gateway_id)
|
||||
.with_gateway_auth_token(auth_token);
|
||||
}
|
||||
|
||||
let pathfinder = ClientPathfinder::new_from_config(&config);
|
||||
let pem_store = PemStore::new(pathfinder);
|
||||
pem_store
|
||||
.write_identity(mix_identity_keys)
|
||||
.expect("Failed to save identity keys");
|
||||
println!("Saved mixnet identity keypair");
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
|
||||
println!(
|
||||
"Unless overridden in all `nym-client run` we will be talking to the following gateway: {}...",
|
||||
config.get_gateway_id(),
|
||||
);
|
||||
if config.get_gateway_auth_token().is_some() {
|
||||
println!(
|
||||
"using optional AuthToken: {:?}",
|
||||
config.get_gateway_auth_token().unwrap()
|
||||
)
|
||||
}
|
||||
println!("Client configuration completed.\n\n\n")
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::{Config, SocketType};
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub mod init;
|
||||
pub mod run;
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
|
||||
if let Some(directory) = matches.value_of("directory") {
|
||||
config = config.with_custom_directory(directory);
|
||||
}
|
||||
|
||||
if let Some(gateway_id) = matches.value_of("gateway") {
|
||||
config = config.with_gateway_id(gateway_id);
|
||||
}
|
||||
|
||||
if matches.is_present("disable-socket") {
|
||||
config = config.with_socket(SocketType::None);
|
||||
}
|
||||
|
||||
if let Some(port) = matches.value_of("port").map(|port| port.parse::<u16>()) {
|
||||
if let Err(err) = port {
|
||||
// if port was overridden, it must be parsable
|
||||
panic!("Invalid port value provided - {:?}", err);
|
||||
}
|
||||
config = config.with_port(port.unwrap());
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::NymClient;
|
||||
use crate::commands::override_config;
|
||||
use crate::config::{persistence::pathfinder::ClientPathfinder, Config};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::identity::MixIdentityKeyPair;
|
||||
use pemstore::pemstore::PemStore;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
App::new("run")
|
||||
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to run.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
// the rest of arguments are optional, they are used to override settings in config file
|
||||
.arg(Arg::with_name("config")
|
||||
.long("config")
|
||||
.help("Custom path to the nym-mixnet-client configuration file")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("directory")
|
||||
.long("directory")
|
||||
.help("Address of the directory server the client is getting topology from")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("gateway")
|
||||
.long("gateway")
|
||||
.help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("disable-socket")
|
||||
.long("disable-socket")
|
||||
.help("Whether to not start the websocket")
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port for the socket (if applicable) to listen on")
|
||||
.takes_value(true)
|
||||
)
|
||||
}
|
||||
|
||||
fn load_identity_keys(config_file: &Config) -> MixIdentityKeyPair {
|
||||
let identity_keypair = PemStore::new(ClientPathfinder::new_from_config(&config_file))
|
||||
.read_identity()
|
||||
.expect("Failed to read stored identity key files");
|
||||
println!(
|
||||
"Public identity key: {}\n",
|
||||
identity_keypair.public_key.to_base58_string()
|
||||
);
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
let id = matches.value_of("id").unwrap();
|
||||
|
||||
let mut config =
|
||||
Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id))
|
||||
.expect("Failed to load config file");
|
||||
|
||||
config = override_config(config, matches);
|
||||
let identity_keypair = load_identity_keys(&config);
|
||||
NymClient::new(config, identity_keypair).run_forever();
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::template::config_template;
|
||||
use config::NymConfig;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::time;
|
||||
|
||||
pub mod persistence;
|
||||
mod template;
|
||||
|
||||
// 'CLIENT'
|
||||
const DEFAULT_LISTENING_PORT: u16 = 1977;
|
||||
const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net";
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: u64 = 1000; // 1s
|
||||
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: u64 = 500; // 0.5s
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: u64 = 200; // 0.2s
|
||||
const DEFAULT_TOPOLOGY_REFRESH_RATE: u64 = 30_000; // 30s
|
||||
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: u64 = 5_000; // 5s
|
||||
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: u64 = 1_500; // 1.5s
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone, Copy)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub enum SocketType {
|
||||
WebSocket,
|
||||
None,
|
||||
}
|
||||
|
||||
impl SocketType {
|
||||
pub fn from_string<S: Into<String>>(val: S) -> Self {
|
||||
let mut upper = val.into();
|
||||
upper.make_ascii_uppercase();
|
||||
match upper.as_ref() {
|
||||
"WEBSOCKET" | "WS" => SocketType::WebSocket,
|
||||
_ => SocketType::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
client: Client,
|
||||
socket: Socket,
|
||||
|
||||
#[serde(default)]
|
||||
logging: Logging,
|
||||
#[serde(default)]
|
||||
debug: Debug,
|
||||
}
|
||||
|
||||
impl NymConfig for Config {
|
||||
fn template() -> &'static str {
|
||||
config_template()
|
||||
}
|
||||
|
||||
fn config_file_name() -> String {
|
||||
"config.toml".to_string()
|
||||
}
|
||||
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("clients")
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.client.nym_root_directory.clone()
|
||||
}
|
||||
|
||||
fn config_directory(&self) -> PathBuf {
|
||||
self.client
|
||||
.nym_root_directory
|
||||
.join(&self.client.id)
|
||||
.join("config")
|
||||
}
|
||||
|
||||
fn data_directory(&self) -> PathBuf {
|
||||
self.client
|
||||
.nym_root_directory
|
||||
.join(&self.client.id)
|
||||
.join("data")
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||
Config::default().with_id(id)
|
||||
}
|
||||
|
||||
// builder methods
|
||||
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self {
|
||||
let id = id.into();
|
||||
if self.client.private_identity_key_file.as_os_str().is_empty() {
|
||||
self.client.private_identity_key_file =
|
||||
self::Client::default_private_identity_key_file(&id);
|
||||
}
|
||||
if self.client.public_identity_key_file.as_os_str().is_empty() {
|
||||
self.client.public_identity_key_file =
|
||||
self::Client::default_public_identity_key_file(&id);
|
||||
}
|
||||
self.client.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_gateway_id<S: Into<String>>(mut self, id: S) -> Self {
|
||||
self.client.gateway_id = id.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_gateway_auth_token<S: Into<String>>(mut self, token: S) -> Self {
|
||||
self.client.gateway_authtoken = Some(token.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_custom_directory<S: Into<String>>(mut self, directory_server: S) -> Self {
|
||||
self.client.directory_server = directory_server.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_socket(mut self, socket_type: SocketType) -> Self {
|
||||
self.socket.socket_type = socket_type;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_port(mut self, port: u16) -> Self {
|
||||
self.socket.listening_port = port;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_high_default_traffic_volume(mut self) -> Self {
|
||||
self.debug.average_packet_delay = 10;
|
||||
self.debug.loop_cover_traffic_average_delay = 20; // 50 cover messages / s
|
||||
self.debug.message_sending_average_delay = 5; // 200 "real" messages / s
|
||||
self
|
||||
}
|
||||
|
||||
// getters
|
||||
pub fn get_config_file_save_location(&self) -> PathBuf {
|
||||
self.config_directory().join(Self::config_file_name())
|
||||
}
|
||||
|
||||
pub fn get_private_identity_key_file(&self) -> PathBuf {
|
||||
self.client.private_identity_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_public_identity_key_file(&self) -> PathBuf {
|
||||
self.client.public_identity_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_directory_server(&self) -> String {
|
||||
self.client.directory_server.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway_id(&self) -> String {
|
||||
self.client.gateway_id.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway_auth_token(&self) -> Option<String> {
|
||||
self.client.gateway_authtoken.clone()
|
||||
}
|
||||
|
||||
pub fn get_socket_type(&self) -> SocketType {
|
||||
self.socket.socket_type
|
||||
}
|
||||
|
||||
pub fn get_listening_port(&self) -> u16 {
|
||||
self.socket.listening_port
|
||||
}
|
||||
|
||||
// Debug getters
|
||||
pub fn get_average_packet_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.average_packet_delay)
|
||||
}
|
||||
|
||||
pub fn get_loop_cover_traffic_average_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.loop_cover_traffic_average_delay)
|
||||
}
|
||||
|
||||
pub fn get_message_sending_average_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.message_sending_average_delay)
|
||||
}
|
||||
|
||||
pub fn get_gateway_response_timeout(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.gateway_response_timeout)
|
||||
}
|
||||
|
||||
pub fn get_topology_refresh_rate(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.topology_refresh_rate)
|
||||
}
|
||||
|
||||
pub fn get_topology_resolution_timeout(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.topology_resolution_timeout)
|
||||
}
|
||||
}
|
||||
|
||||
fn de_option_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if s.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(s))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Client {
|
||||
/// ID specifies the human readable ID of this particular client.
|
||||
id: String,
|
||||
|
||||
/// URL to the directory server.
|
||||
directory_server: String,
|
||||
|
||||
/// Path to file containing private identity key.
|
||||
private_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public identity key.
|
||||
public_identity_key_file: PathBuf,
|
||||
|
||||
/// gateway_id specifies ID of the gateway to which the client should send messages.
|
||||
/// If initially omitted, a random gateway will be chosen from the available topology.
|
||||
gateway_id: String,
|
||||
|
||||
/// A gateway specific, optional, base58 stringified authentication token used for
|
||||
/// communication with particular gateway.
|
||||
#[serde(deserialize_with = "de_option_string")]
|
||||
gateway_authtoken: Option<String>,
|
||||
|
||||
/// nym_home_directory specifies absolute path to the home nym Clients directory.
|
||||
/// It is expected to use default value and hence .toml file should not redefine this field.
|
||||
nym_root_directory: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for Client {
|
||||
fn default() -> Self {
|
||||
// there must be explicit checks for whether id is not empty later
|
||||
Client {
|
||||
id: "".to_string(),
|
||||
directory_server: DEFAULT_DIRECTORY_SERVER.to_string(),
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
gateway_id: "".to_string(),
|
||||
gateway_authtoken: None,
|
||||
nym_root_directory: Config::default_root_directory(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
fn default_private_identity_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("private_identity.pem")
|
||||
}
|
||||
|
||||
fn default_public_identity_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("public_identity.pem")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Socket {
|
||||
socket_type: SocketType,
|
||||
listening_port: u16,
|
||||
}
|
||||
|
||||
impl Default for Socket {
|
||||
fn default() -> Self {
|
||||
Socket {
|
||||
socket_type: SocketType::WebSocket,
|
||||
listening_port: DEFAULT_LISTENING_PORT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Logging {}
|
||||
|
||||
impl Default for Logging {
|
||||
fn default() -> Self {
|
||||
Logging {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Debug {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
average_packet_delay: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
loop_cover_traffic_average_delay: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
message_sending_average_delay: u64,
|
||||
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
gateway_response_timeout: u64,
|
||||
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
topology_refresh_rate: u64,
|
||||
|
||||
/// During topology refresh, test packets are sent through every single possible network
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
topology_resolution_timeout: u64,
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
fn default() -> Self {
|
||||
Debug {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
|
||||
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod client_config {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn after_saving_default_config_the_loaded_one_is_identical() {
|
||||
// need to figure out how to do something similar but without touching the disk
|
||||
// or the file system at all...
|
||||
let temp_location = tempfile::tempdir().unwrap().path().join("config.toml");
|
||||
let default_config = Config::default().with_id("foomp".to_string());
|
||||
default_config
|
||||
.save_to_file(Some(temp_location.clone()))
|
||||
.unwrap();
|
||||
|
||||
let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap();
|
||||
|
||||
assert_eq!(default_config, loaded_config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod pathfinder;
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::config::Config;
|
||||
use pemstore::pathfinder::PathFinder;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientPathfinder {
|
||||
pub config_dir: PathBuf,
|
||||
pub private_mix_key: PathBuf,
|
||||
pub public_mix_key: PathBuf,
|
||||
}
|
||||
|
||||
impl ClientPathfinder {
|
||||
pub fn new(id: String) -> Self {
|
||||
let os_config_dir = dirs::config_dir().unwrap(); // grabs the OS default config dir
|
||||
let config_dir = os_config_dir.join("nym").join("clients").join(id);
|
||||
let private_mix_key = config_dir.join("private.pem");
|
||||
let public_mix_key = config_dir.join("public.pem");
|
||||
ClientPathfinder {
|
||||
config_dir,
|
||||
private_mix_key,
|
||||
public_mix_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_config(config: &Config) -> Self {
|
||||
ClientPathfinder {
|
||||
config_dir: config.get_config_file_save_location(),
|
||||
private_mix_key: config.get_private_identity_key_file(),
|
||||
public_mix_key: config.get_public_identity_key_file(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PathFinder for ClientPathfinder {
|
||||
fn config_dir(&self) -> PathBuf {
|
||||
self.config_dir.clone()
|
||||
}
|
||||
|
||||
fn private_identity_key(&self) -> PathBuf {
|
||||
self.private_mix_key.clone()
|
||||
}
|
||||
|
||||
fn public_identity_key(&self) -> PathBuf {
|
||||
self.public_mix_key.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub(crate) fn config_template() -> &'static str {
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
// I think it's useful to have comments attached to the saved config file to explain behaviour of
|
||||
// particular fields.
|
||||
// Note: any changes to the template must be reflected in the appropriate structs in mod.rs.
|
||||
r#"
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
|
||||
##### main base client config options #####
|
||||
|
||||
[client]
|
||||
# Human readable ID of this particular client.
|
||||
id = '{{ client.id }}'
|
||||
|
||||
# URL to the directory server.
|
||||
directory_server = '{{ client.directory_server }}'
|
||||
|
||||
# Path to file containing private identity key.
|
||||
private_identity_key_file = '{{ client.private_identity_key_file }}'
|
||||
|
||||
# Path to file containing public identity key.
|
||||
public_identity_key_file = '{{ client.public_identity_key_file }}'
|
||||
|
||||
##### additional client config options #####
|
||||
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
gateway_id = '{{ client.gateway_id }}'
|
||||
|
||||
# A gateway specific, optional, base58 stringified authentication token used for
|
||||
# communication with particular gateway.
|
||||
gateway_authtoken = '{{ client.gateway_authtoken }}'
|
||||
|
||||
##### advanced configuration options #####
|
||||
|
||||
# Absolute path to the home Nym Clients directory.
|
||||
nym_root_directory = '{{ client.nym_root_directory }}'
|
||||
|
||||
|
||||
##### socket config options #####
|
||||
|
||||
[socket]
|
||||
|
||||
# allowed values are 'WebSocket' or 'None'
|
||||
socket_type = '{{ socket.socket_type }}'
|
||||
|
||||
# if applicable (for the case of 'WebSocket'), the port on which the client
|
||||
# will be listening for incoming requests
|
||||
listening_port = {{ socket.listening_port }}
|
||||
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
[logging]
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
##### debug configuration options #####
|
||||
# The following options should not be modified unless you know EXACTLY what you are doing
|
||||
# as if set incorrectly, they may impact your anonymity.
|
||||
|
||||
[debug]
|
||||
|
||||
average_packet_delay = {{ debug.average_packet_delay }}
|
||||
loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }}
|
||||
message_sending_average_delay = {{ debug.message_sending_average_delay }}
|
||||
|
||||
"#
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod built_info;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod websocket;
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use clap::{App, ArgMatches};
|
||||
|
||||
pub mod built_info;
|
||||
pub mod client;
|
||||
mod commands;
|
||||
pub mod config;
|
||||
pub mod websocket;
|
||||
|
||||
fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let arg_matches = App::new("Nym Client")
|
||||
.version(built_info::PKG_VERSION)
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Nym Client")
|
||||
.subcommand(commands::init::command_args())
|
||||
.subcommand(commands::run::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches);
|
||||
}
|
||||
|
||||
fn execute(matches: ArgMatches) {
|
||||
match matches.subcommand() {
|
||||
("init", Some(m)) => commands::init::execute(m),
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() -> &'static str {
|
||||
"usage: --help to see available options.\n\n"
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
format!(
|
||||
r#"
|
||||
|
||||
_ __ _ _ _ __ ___
|
||||
| '_ \| | | | '_ \ _ \
|
||||
| | | | |_| | | | | | |
|
||||
|_| |_|\__, |_| |_| |_|
|
||||
|___/
|
||||
|
||||
(client - version {:})
|
||||
|
||||
"#,
|
||||
built_info::PKG_VERSION
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::types::{BinaryClientRequest, ClientRequest, ServerResponse};
|
||||
use crate::client::{
|
||||
inbound_messages::{InputMessage, InputMessageSender},
|
||||
received_buffer::{
|
||||
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
|
||||
},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use futures::channel::mpsc;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::chunking::split_and_prepare_payloads;
|
||||
use std::convert::TryFrom;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{
|
||||
accept_async,
|
||||
tungstenite::{protocol::Message, Error as WsError},
|
||||
WebSocketStream,
|
||||
};
|
||||
use topology::NymTopology;
|
||||
|
||||
enum ReceivedResponseType {
|
||||
Binary,
|
||||
Text,
|
||||
}
|
||||
|
||||
impl Default for ReceivedResponseType {
|
||||
fn default() -> Self {
|
||||
ReceivedResponseType::Binary
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Handler<T: NymTopology> {
|
||||
msg_input: InputMessageSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: Recipient,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
socket: Option<WebSocketStream<TcpStream>>,
|
||||
received_response_type: ReceivedResponseType,
|
||||
}
|
||||
|
||||
// clone is used to use handler on a new connection, which initially is `None`
|
||||
impl<T: NymTopology> Clone for Handler<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Handler {
|
||||
msg_input: self.msg_input.clone(),
|
||||
buffer_requester: self.buffer_requester.clone(),
|
||||
self_full_address: self.self_full_address.clone(),
|
||||
topology_accessor: self.topology_accessor.clone(),
|
||||
socket: None,
|
||||
received_response_type: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NymTopology> Drop for Handler<T> {
|
||||
fn drop(&mut self) {
|
||||
self.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
||||
.expect("the buffer request failed!")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NymTopology> Handler<T> {
|
||||
pub(crate) fn new(
|
||||
msg_input: InputMessageSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: Recipient,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
) -> Self {
|
||||
Handler {
|
||||
msg_input,
|
||||
buffer_requester,
|
||||
self_full_address,
|
||||
topology_accessor,
|
||||
socket: None,
|
||||
received_response_type: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_text_send(&mut self, msg: String, full_recipient_address: String) -> ServerResponse {
|
||||
let message_bytes = msg.into_bytes();
|
||||
|
||||
let recipient = match Recipient::try_from_string(full_recipient_address) {
|
||||
Ok(address) => address,
|
||||
Err(e) => {
|
||||
trace!("failed to parse received Recipient: {:?}", e);
|
||||
return ServerResponse::new_error("malformed recipient address");
|
||||
}
|
||||
};
|
||||
|
||||
// in case the message is too long and needs to be split into multiple packets:
|
||||
let split_message = split_and_prepare_payloads(&message_bytes);
|
||||
for message_fragment in split_message {
|
||||
let input_msg = InputMessage::new(recipient.clone(), message_fragment);
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
}
|
||||
|
||||
self.received_response_type = ReceivedResponseType::Text;
|
||||
|
||||
ServerResponse::Send
|
||||
}
|
||||
|
||||
async fn handle_text_get_clients(&mut self) -> ServerResponse {
|
||||
match self.topology_accessor.get_all_clients().await {
|
||||
Some(clients) => {
|
||||
let client_keys = clients.into_iter().map(|client| client.pub_key).collect();
|
||||
ServerResponse::GetClients {
|
||||
clients: client_keys,
|
||||
}
|
||||
}
|
||||
None => ServerResponse::new_error("invalid network topology"),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_text_self_address(&self) -> ServerResponse {
|
||||
ServerResponse::SelfAddress {
|
||||
address: self.self_full_address.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_text_message(&mut self, msg: String) -> Message {
|
||||
debug!("Handling text message request");
|
||||
trace!("Content: {:?}", msg.clone());
|
||||
|
||||
match ClientRequest::try_from(msg) {
|
||||
Err(e) => ServerResponse::Error {
|
||||
message: format!("received invalid request. err: {:?}", e),
|
||||
}
|
||||
.into(),
|
||||
Ok(req) => match req {
|
||||
ClientRequest::Send { message, recipient } => {
|
||||
self.handle_text_send(message, recipient)
|
||||
}
|
||||
ClientRequest::GetClients => self.handle_text_get_clients().await,
|
||||
ClientRequest::SelfAddress => self.handle_text_self_address(),
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_binary_send(&mut self, recipient: Recipient, data: Vec<u8>) -> ServerResponse {
|
||||
// in case the message is too long and needs to be split into multiple packets:
|
||||
let split_message = split_and_prepare_payloads(&data);
|
||||
for message_fragment in split_message {
|
||||
let input_msg = InputMessage::new(recipient.clone(), message_fragment);
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
}
|
||||
|
||||
self.received_response_type = ReceivedResponseType::Binary;
|
||||
ServerResponse::Send
|
||||
}
|
||||
|
||||
// if it's binary we assume it's a sphinx packet formatted the same way as we'd have sent
|
||||
// it to the gateway
|
||||
async fn handle_binary_message(&mut self, msg: Vec<u8>) -> Message {
|
||||
debug!("Handling binary message request");
|
||||
|
||||
self.received_response_type = ReceivedResponseType::Binary;
|
||||
// make sure it is correctly formatted
|
||||
let binary_request = BinaryClientRequest::try_from_bytes(&msg);
|
||||
if binary_request.is_none() {
|
||||
return ServerResponse::new_error("invalid binary request").into();
|
||||
}
|
||||
match binary_request.unwrap() {
|
||||
BinaryClientRequest::Send { recipient, data } => {
|
||||
self.handle_binary_send(recipient, data).await
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
async fn handle_request(&mut self, raw_request: Message) -> Option<Message> {
|
||||
// apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore
|
||||
// them and let's test that claim. If that's not the case, just copy code from
|
||||
// old version of this file.
|
||||
match raw_request {
|
||||
Message::Text(text_message) => Some(self.handle_text_message(text_message).await),
|
||||
Message::Binary(binary_message) => {
|
||||
Some(self.handle_binary_message(binary_message).await)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn push_websocket_received_plaintexts(
|
||||
&mut self,
|
||||
messages_bytes: Vec<Vec<u8>>,
|
||||
) -> Result<(), WsError> {
|
||||
let response_messages: Vec<_> = match self.received_response_type {
|
||||
ReceivedResponseType::Binary => messages_bytes
|
||||
.into_iter()
|
||||
.map(|msg| Ok(Message::Binary(msg)))
|
||||
.collect(),
|
||||
ReceivedResponseType::Text => {
|
||||
let mut decoded_messages = Vec::new();
|
||||
// either all succeed or all fall back
|
||||
let mut did_fail = false;
|
||||
for message in messages_bytes.iter() {
|
||||
match std::str::from_utf8(message) {
|
||||
Ok(msg) => decoded_messages.push(msg),
|
||||
Err(err) => {
|
||||
did_fail = true;
|
||||
warn!("Invalid UTF-8 sequence in response message - {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if did_fail {
|
||||
messages_bytes
|
||||
.into_iter()
|
||||
.map(|msg| Ok(Message::Binary(msg)))
|
||||
.collect()
|
||||
} else {
|
||||
decoded_messages
|
||||
.into_iter()
|
||||
.map(|msg| Ok(Message::Text(msg.to_string())))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut send_stream = futures::stream::iter(response_messages);
|
||||
self.socket
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send_all(&mut send_stream)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_websocket_response(&mut self, msg: Message) -> Result<(), WsError> {
|
||||
match self.socket {
|
||||
// TODO: more closely investigate difference between `Sink::send` and `Sink::send_all`
|
||||
// it got something to do with batching and flushing - it might be important if it
|
||||
// turns out somehow we've got a bottleneck here
|
||||
Some(ref mut ws_stream) => ws_stream.send(msg).await,
|
||||
_ => panic!("impossible state - websocket handshake was somehow reverted"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn next_websocket_request(&mut self) -> Option<Result<Message, WsError>> {
|
||||
match self.socket {
|
||||
Some(ref mut ws_stream) => ws_stream.next().await,
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_for_requests(&mut self, mut msg_receiver: ReconstructedMessagesReceiver) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
socket_msg = self.next_websocket_request() => {
|
||||
if socket_msg.is_none() {
|
||||
break;
|
||||
}
|
||||
let socket_msg = match socket_msg.unwrap() {
|
||||
Ok(socket_msg) => socket_msg,
|
||||
Err(err) => {
|
||||
error!("failed to obtain message from websocket stream! stopping connection handler: {}", err);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if socket_msg.is_close() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(response) = self.handle_request(socket_msg).await {
|
||||
if let Err(err) = self.send_websocket_response(response).await {
|
||||
warn!(
|
||||
"Failed to send message over websocket: {}. Assuming the connection is dead.",
|
||||
err
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mix_messages = msg_receiver.next() => {
|
||||
let mix_messages = mix_messages.expect(
|
||||
"mix messages sender was unexpectedly closed! this shouldn't have ever happened!",
|
||||
);
|
||||
if let Err(e) = self.push_websocket_received_plaintexts(mix_messages).await {
|
||||
warn!("failed to send sphinx packets back to the client - {:?}, assuming the connection is dead", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// consume self to make sure `drop` is called after this is done
|
||||
pub(crate) async fn handle_connection(mut self, socket: TcpStream) {
|
||||
let ws_stream = match accept_async(socket).await {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
Err(err) => {
|
||||
warn!("error while performing the websocket handshake - {:?}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.socket = Some(ws_stream);
|
||||
|
||||
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
|
||||
|
||||
// tell the buffer to start sending stuff to us
|
||||
self.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
.expect("the buffer request failed!");
|
||||
|
||||
self.listen_for_requests(reconstructed_receiver).await;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::handler::Handler;
|
||||
use log::*;
|
||||
use std::{
|
||||
net::{Shutdown, SocketAddr},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::runtime;
|
||||
use tokio::{sync::Notify, task::JoinHandle};
|
||||
use topology::NymTopology;
|
||||
|
||||
enum State {
|
||||
Connected,
|
||||
AwaitingConnection,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn is_connected(&self) -> bool {
|
||||
match self {
|
||||
State::Connected => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Listener {
|
||||
address: SocketAddr,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(port: u16) -> Self {
|
||||
Listener {
|
||||
// unless we find compelling reason not to, just listen on local only
|
||||
address: SocketAddr::new("127.0.0.1".parse().unwrap(), port),
|
||||
state: State::AwaitingConnection,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run<T: NymTopology + 'static>(&mut self, handler: Handler<T>) {
|
||||
let mut tcp_listener = tokio::net::TcpListener::bind(self.address)
|
||||
.await
|
||||
.expect("Failed to start websocket listener");
|
||||
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = notify.notified() => {
|
||||
// our connection terminated - we are open to a new one now!
|
||||
self.state = State::AwaitingConnection;
|
||||
}
|
||||
new_conn = tcp_listener.accept() => {
|
||||
match new_conn {
|
||||
Ok((socket, remote_addr)) => {
|
||||
debug!("Received connection from {:?}", remote_addr);
|
||||
if self.state.is_connected() {
|
||||
warn!("tried to duplicate!");
|
||||
// if we've already got a connection, don't allow another one
|
||||
debug!("but there was already a connection present!");
|
||||
// while we only ever want to accept a single connection, we don't want
|
||||
// to leave clients hanging (and also allow for reconnection if it somehow
|
||||
// was dropped)
|
||||
match socket.shutdown(Shutdown::Both) {
|
||||
Ok(_) => trace!(
|
||||
"closed the connection between attempting websocket handshake"
|
||||
),
|
||||
Err(e) => warn!("failed to cleanly close the connection - {:?}", e),
|
||||
};
|
||||
} else {
|
||||
// even though we're spawning a new task with the handler here, we will only ever spawn a single one.
|
||||
// it's done so that any new connections to this listener could be rejected rather than left
|
||||
// hanging because the executor doesn't come back here
|
||||
let notify_clone = Arc::clone(¬ify);
|
||||
let fresh_handler = handler.clone();
|
||||
tokio::spawn(async move {
|
||||
fresh_handler.handle_connection(socket).await;
|
||||
notify_clone.notify();
|
||||
});
|
||||
self.state = State::Connected;
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("failed to get client: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start<T: NymTopology + 'static>(
|
||||
mut self,
|
||||
rt_handle: &runtime::Handle,
|
||||
handler: Handler<T>,
|
||||
) -> JoinHandle<()> {
|
||||
info!("Running websocket on {:?}", self.address.to_string());
|
||||
|
||||
rt_handle.spawn(async move { self.run(handler).await })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub(crate) mod handler;
|
||||
pub(crate) mod listener;
|
||||
pub(crate) mod types;
|
||||
|
||||
pub(crate) use handler::Handler;
|
||||
pub(crate) use listener::Listener;
|
||||
|
||||
pub use types::{BinaryClientRequest, ClientRequest, ServerResponse};
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum ClientRequest {
|
||||
Send { message: String, recipient: String },
|
||||
GetClients,
|
||||
SelfAddress,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ClientRequest {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(msg: String) -> Result<Self, Self::Error> {
|
||||
serde_json::from_str(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Message> for ClientRequest {
|
||||
fn into(self) -> Message {
|
||||
let str_req = serde_json::to_string(&self).unwrap();
|
||||
Message::Text(str_req)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum BinaryClientRequest {
|
||||
Send { recipient: Recipient, data: Vec<u8> },
|
||||
}
|
||||
|
||||
impl BinaryClientRequest {
|
||||
// TODO: perhaps do it the proper way and introduce an error type
|
||||
pub fn try_from_bytes(req: &[u8]) -> Option<Self> {
|
||||
if req.len() < Recipient::LEN {
|
||||
return None;
|
||||
}
|
||||
let mut recipient_bytes = [0u8; Recipient::LEN];
|
||||
recipient_bytes.copy_from_slice(&req[..Recipient::LEN]);
|
||||
let recipient = Recipient::from_bytes(recipient_bytes);
|
||||
|
||||
Some(BinaryClientRequest::Send {
|
||||
recipient,
|
||||
data: req[Recipient::LEN..].to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
Self::Send { recipient, data } => recipient
|
||||
.into_bytes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(data.into_iter())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Message> for BinaryClientRequest {
|
||||
fn into(self) -> Message {
|
||||
Message::Binary(self.into_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum ServerResponse {
|
||||
Send,
|
||||
Received { messages: Vec<String> },
|
||||
GetClients { clients: Vec<String> },
|
||||
SelfAddress { address: String },
|
||||
Error { message: String },
|
||||
}
|
||||
|
||||
impl ServerResponse {
|
||||
pub fn new_error<S: Into<String>>(msg: S) -> Self {
|
||||
ServerResponse::Error {
|
||||
message: msg.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ServerResponse {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(msg: String) -> Result<Self, <ServerResponse as TryFrom<String>>::Error> {
|
||||
serde_json::from_str(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Message> for ServerResponse {
|
||||
fn into(self) -> Message {
|
||||
// it should be safe to call `unwrap` here as the message is generated by the server
|
||||
// so if it fails (and consequently panics) it's a bug that should be resolved
|
||||
let str_res = serde_json::to_string(&self).unwrap();
|
||||
Message::Text(str_res)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
install:
|
||||
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --locked
|
||||
@@ -0,0 +1,7 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
node_modules
|
||||
@@ -0,0 +1,69 @@
|
||||
language: rust
|
||||
sudo: false
|
||||
|
||||
cache: cargo
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
||||
# Builds with wasm-pack.
|
||||
- rust: beta
|
||||
env: RUST_BACKTRACE=1
|
||||
addons:
|
||||
firefox: latest
|
||||
chrome: stable
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
# Having a broken Cargo.toml (in that it has curlies in fields) anywhere
|
||||
# in any of our parent dirs is problematic.
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- wasm-pack build
|
||||
- wasm-pack test --chrome --firefox --headless
|
||||
|
||||
# Builds on nightly.
|
||||
- rust: nightly
|
||||
env: RUST_BACKTRACE=1
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- rustup target add wasm32-unknown-unknown
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- cargo check
|
||||
- cargo check --target wasm32-unknown-unknown
|
||||
- cargo check --no-default-features
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features
|
||||
- cargo check --no-default-features --features console_error_panic_hook
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
|
||||
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"
|
||||
|
||||
# Builds on beta.
|
||||
- rust: beta
|
||||
env: RUST_BACKTRACE=1
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- rustup target add wasm32-unknown-unknown
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- cargo check
|
||||
- cargo check --target wasm32-unknown-unknown
|
||||
- cargo check --no-default-features
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features
|
||||
- cargo check --no-default-features --features console_error_panic_hook
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
|
||||
# Note: no enabling the `wee_alloc` feature here because it requires
|
||||
# nightly for now.
|
||||
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "nym-client-wasm"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
version = "0.7.5"
|
||||
edition = "2018"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/nymtech/nym"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
slice_as_array = "1.1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
rand = "0.7.2"
|
||||
|
||||
# internal
|
||||
crypto = { path = "../../common/crypto" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
topology = { path = "../../common/topology" }
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1.1", optional = true }
|
||||
|
||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||
# compared to the default allocator's ~10K. It is slower than the default
|
||||
# allocator, however.
|
||||
#
|
||||
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||
wee_alloc = { version = "0.4.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.2"
|
||||
@@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,42 @@
|
||||
# Nym Sphinx webassembly client
|
||||
|
||||
Produces layer-encrypted Sphinx packets for use with Nym mixnets.
|
||||
|
||||
[Sphinx](http://www0.cs.ucl.ac.uk/staff/G.Danezis/papers/sphinx-eprint.pdf) packets ensure the privacy of information in transit, even when the adversary is able to monitor the network in its entirety. When used with a mixnet, both content (what you said) and metadata (who you said it to, when you said it) are protected.
|
||||
|
||||
This helps browser-based and mobile applications get stronger privacy, in a way that wasn't previously possible.
|
||||
|
||||
This client is part of the [Nym](https://nymtech.net/docs) project. It's written in Rust and compiled to WebAssembly.
|
||||
|
||||
## Security Status
|
||||
|
||||
From a security point of view, this module is not yet complete. A key missing feature, cover traffic, will be implemented soon. You can build your applications, but don't rely on it for strong anonymity yet if your application needs cover traffic.
|
||||
|
||||
## Using it
|
||||
|
||||
See the [Nym docs](https://nymtech.net/docs).
|
||||
|
||||
### Demo
|
||||
|
||||
There's a demo web application in the `js-example` folder. To run it, first make sure you've got a recent `npm` installed, then follow the instructions in its README.
|
||||
|
||||
## Developing
|
||||
|
||||
This is a Rust crate which is set up to automatically cross-compile the contents of `src` to WebAssembly (aka wasm). It's published from the main [Nym platform monorepo](https://github.com/nymtech/nym) in the `clients/webassembly` directory.
|
||||
|
||||
First, make sure you've got all the [Rust wasm toolchain](https://rustwasm.github.io/book/game-of-life/setup.html) installed. Cross-compilation sounds scary but the Rust crew have enabled a remarkably simple setup.
|
||||
|
||||
Whenever you change any Rust in the `src` directory, run `wasm-pack build --scope nymproject` to update the built wasm artefact in the `pkg` directory.
|
||||
|
||||
For now, when you compile `nym-client-wasm` using `wasm-pack build --scope nymproject` you will need to manually copy the file `client.js` into the `pkg` and `package.json` with entries from `package.json` on the main path. Once [these](https://github.com/rustwasm/wasm-pack/issues/840) [issues](https://github.com/rustwasm/rfcs/pull/8#issuecomment-564725214) get closed, this annoying extra step will go away.
|
||||
|
||||
To be clear, this is not something that most JS developers need to worry about, this is only for Nym devs. The packages on NPM have all files in place. Just install and enjoy!
|
||||
|
||||
### Packaging
|
||||
|
||||
If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it:
|
||||
|
||||
1. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
|
||||
2. copy `client.js` into the `pkg` folder and update `package.json` manifest with the provided one
|
||||
3. bump version numbers as necessary for SemVer
|
||||
4. `wasm-pack publish --access=public` will publish your changed package to NPM
|
||||
@@ -0,0 +1,330 @@
|
||||
import * as wasm from ".";
|
||||
import { version } from './package.json';
|
||||
import { major, minor, parse } from "semver";
|
||||
/**
|
||||
* A Nym identity, consisting of a public/private keypair and a Nym
|
||||
* gateway address.
|
||||
*/
|
||||
export class Identity {
|
||||
// in the future this should allow for loading from local storage
|
||||
constructor() {
|
||||
const raw_identity = JSON.parse(wasm.keygen());
|
||||
this.address = raw_identity.address;
|
||||
this.privateKey = raw_identity.private_key;
|
||||
this.publicKey = raw_identity.public_key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Client which connects to a Nym gateway via websocket. All communication
|
||||
* with the Nym network happens through this connection.
|
||||
*/
|
||||
export class Client {
|
||||
constructor(directoryUrl, identity, authToken) {
|
||||
this.authToken = authToken
|
||||
this.gateway = null; // {socketAddress, mixAddress, conn}
|
||||
this.identity = identity;
|
||||
this.topology = null;
|
||||
this.topologyEndpoint = directoryUrl + "/api/presence/topology";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} a user-pubkey@nym-gateway recipient address
|
||||
*/
|
||||
formatAsRecipient() {
|
||||
return `${this.identity.address}@${this.gateway.mixAddress}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current network topology, then connect to this client's Nym gateway
|
||||
* via websocket.
|
||||
*/
|
||||
async start() {
|
||||
await this.updateTopology();
|
||||
this._getInitialGateway();
|
||||
await this.connect();
|
||||
// TODO: a way to somehow await for our authenticate response to be processed
|
||||
}
|
||||
|
||||
_isRegistered() {
|
||||
return this.authToken !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given node is compatible with this client
|
||||
*/
|
||||
isNodeVersionCompatible(node) {
|
||||
const clientVersion = parse(version);
|
||||
const mixVersion = parse(node.version);
|
||||
return major(clientVersion) === major(mixVersion) && minor(clientVersion) === minor(mixVersion)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Nym network topology.
|
||||
*
|
||||
* @returns an object containing the current Nym network topology
|
||||
*/
|
||||
async updateTopology() {
|
||||
let response = await http('get', this.topologyEndpoint);
|
||||
let topology = JSON.parse(response); // make sure it's a valid json
|
||||
topology.mixNodes = topology.mixNodes.filter(this.isNodeVersionCompatible)
|
||||
topology.gatewayNodes = topology.gatewayNodes.filter(this.isNodeVersionCompatible)
|
||||
this.topology = topology;
|
||||
this.onUpdatedTopology();
|
||||
return topology;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the address of a Nym gateway to send the Sphinx packet to.
|
||||
* At present we choose the first gateway as the network should only be
|
||||
* running one. Later, we will implement multiple gateways.
|
||||
*/
|
||||
_getInitialGateway() {
|
||||
if (this.gateway !== null) {
|
||||
console.error("tried to re-initialise gateway data");
|
||||
return;
|
||||
}
|
||||
if (this.topology === null || this.topology.gatewayNodes.length === 0) {
|
||||
console.error("No gateways available on the network")
|
||||
}
|
||||
this.gateway = {
|
||||
socketAddress: this.topology.gatewayNodes[0].clientListener,
|
||||
mixAddress: this.topology.gatewayNodes[0].pubKey,
|
||||
conn: null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the client's defined Nym gateway via websocket.
|
||||
*/
|
||||
connect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new WebSocket(this.gateway.socketAddress);
|
||||
conn.onclose = this.onConnectionClose;
|
||||
conn.onerror = (event) => {
|
||||
this.onConnectionError(event);
|
||||
reject(event);
|
||||
};
|
||||
conn.onmessage = (event) => this.onMessage(event);
|
||||
conn.onopen = (event) => {
|
||||
this.onConnect(event);
|
||||
if (this._isRegistered()) {
|
||||
this.sendAuthenticateRequest();
|
||||
resolve(); // TODO: we should wait for authenticateResponse...
|
||||
} else {
|
||||
this.sendRegisterRequest();
|
||||
resolve(); // TODO: we should wait for registerResponse...
|
||||
}
|
||||
}
|
||||
|
||||
this.gateway.conn = conn;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a registration request to a Nym gateway node. Use it only if you
|
||||
* haven't registered this client before.
|
||||
*/
|
||||
sendRegisterRequest() {
|
||||
const registerReq = buildRegisterRequest(this.identity.address);
|
||||
this.gateway.conn.send(registerReq);
|
||||
this.onRegisterRequestSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates with a Nym gateway for this client.
|
||||
*
|
||||
* @param {string} token
|
||||
*/
|
||||
sendAuthenticateRequest(token) {
|
||||
const authenticateReq = buildAuthenticateRequest(this.identity.address, token);
|
||||
this.conn.send(authenticateReq);
|
||||
this.onAuthenticateRequestSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message up the websocket to this client's Nym gateway.
|
||||
*
|
||||
* NOTE: this currently does not implement chunking and messages over ~1KB
|
||||
* will cause a panic. This will be fixed in a future version.
|
||||
*
|
||||
* `message` must be a {string} at the moment. Binary `Blob` and `ArrayBuffer`
|
||||
* will be supported soon.
|
||||
*
|
||||
* @param {*} message
|
||||
* @param {string} recipient
|
||||
*/
|
||||
sendMessage(message, recipient) {
|
||||
if (this.gateway === null || this.gateway.conn === null) {
|
||||
console.error("Client was not initialised");
|
||||
return
|
||||
}
|
||||
if (message instanceof Blob || message instanceof ArrayBuffer) {
|
||||
// but it wouldn't be difficult to implement it.
|
||||
console.error("Binary messages are not yet supported");
|
||||
return
|
||||
}
|
||||
const sphinxPacket = wasm.create_sphinx_packet(JSON.stringify(this.topology), message, recipient);
|
||||
this.gateway.conn.send(sphinxPacket);
|
||||
this.onMessageSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback triggered when a message is received from this client's Nym
|
||||
* gateway.
|
||||
*
|
||||
* The `event` may be a binary blob which was the payload of a Sphinx packet,
|
||||
* or it may be a JSON control message (for example, the result of an
|
||||
* authenticate request).
|
||||
* @param {*} event
|
||||
*/
|
||||
onMessage(event) {
|
||||
if (event.data instanceof Blob) {
|
||||
this.onBlobResponse(event);
|
||||
} else {
|
||||
const receivedData = JSON.parse(event.data);
|
||||
switch (receivedData.type) {
|
||||
case "send": return this.onSendConfirmation(event);
|
||||
case "authenticate": return this.onAuthenticateResponse(event);
|
||||
case "error": return this.onErrorResponse(event);
|
||||
case "register": return this.onRegisterResponse(event);
|
||||
default: return this.onUnknownResponse(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all the callbacks that can be overwritten
|
||||
|
||||
/**
|
||||
* A callback that fires when network topology is updated.
|
||||
*/
|
||||
onUpdatedTopology() {
|
||||
console.log("Default: Updated topology")
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
onConnect(event) {
|
||||
console.log("Default: Established gateway connection", event);
|
||||
}
|
||||
|
||||
onConnectionClose(event) {
|
||||
console.log("Default: The the connection to gateway was closed", event);
|
||||
}
|
||||
|
||||
onConnectionError(event) {
|
||||
console.error("Default: Gateway connection error: ", event);
|
||||
}
|
||||
|
||||
onAuthenticateRequestSend() {
|
||||
console.log("Default: sent authentication request");
|
||||
}
|
||||
|
||||
onRegisterRequestSend() {
|
||||
console.log("Default: sent register request");
|
||||
}
|
||||
|
||||
onMessageSend() {
|
||||
console.log("Default: sent message through gateway to the mixnet");
|
||||
}
|
||||
|
||||
onAuthenticated() {
|
||||
console.log("Default: we are authenticated");
|
||||
}
|
||||
|
||||
onSendConfirmation(event) {
|
||||
console.log("Default: received send confirmation", event.data);
|
||||
}
|
||||
|
||||
onRegisterResponse(event) {
|
||||
console.log("Default: received register response", event.data);
|
||||
}
|
||||
|
||||
onAuthenticateResponse(event) {
|
||||
console.log("Default: received authentication response", event.data);
|
||||
}
|
||||
|
||||
onErrorResponse(event) {
|
||||
console.error("Received error response", event.data);
|
||||
}
|
||||
|
||||
onUnknownResponse(event) {
|
||||
console.error("Received unknown response", event);
|
||||
}
|
||||
|
||||
// Gateway returns any received data from the mix network as a Blob,
|
||||
// So most likely this is your best bet to override
|
||||
onBlobResponse(event) {
|
||||
// note that the actual handling depends on the expected content, however,
|
||||
// in this example we're just sending a text messages to ourselves and hence
|
||||
// we can safely read it as text
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
this.onText(reader.result)
|
||||
};
|
||||
|
||||
reader.readAsText(event.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback that makes a best-effort attempt to return decrypted Sphinx bytes as text.
|
||||
*
|
||||
* Note that no checks are performed to determine whether something is
|
||||
* really text. If the received data is in fact binary, you'll get
|
||||
* binary-as-text from this callback.
|
||||
*/
|
||||
onText(data) {
|
||||
console.log("Default: parsed the following data", data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a JSON registration request.
|
||||
*
|
||||
* @param {string} address
|
||||
*/
|
||||
function buildRegisterRequest(address) {
|
||||
return JSON.stringify({ "type": "register", "address": address });
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a JSON authentication request.
|
||||
*
|
||||
* @param {string} address
|
||||
* @param {string} token
|
||||
*/
|
||||
function buildAuthenticateRequest(address, token) {
|
||||
return JSON.stringify({ "type": "authenticate", "address": address, "token": token });
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP request.
|
||||
* @param {string} method
|
||||
* @param {string} url
|
||||
*/
|
||||
function http(method, url) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url);
|
||||
xhr.onload = function () {
|
||||
if (this.status >= 200 && this.status < 300) {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
reject({
|
||||
status: this.status,
|
||||
statusText: xhr.statusText
|
||||
});
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
reject({
|
||||
status: this.status,
|
||||
statusText: xhr.statusText
|
||||
});
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawn } = require("child_process");
|
||||
const fs = require("fs");
|
||||
|
||||
let folderName = '.';
|
||||
|
||||
if (process.argv.length >= 3) {
|
||||
folderName = process.argv[2];
|
||||
if (!fs.existsSync(folderName)) {
|
||||
fs.mkdirSync(folderName);
|
||||
}
|
||||
}
|
||||
|
||||
const clone = spawn("git", ["clone", "https://github.com/rustwasm/create-wasm-app.git", folderName]);
|
||||
|
||||
clone.on("close", code => {
|
||||
if (code !== 0) {
|
||||
console.error("cloning the template failed!")
|
||||
process.exit(code);
|
||||
} else {
|
||||
console.log("🦀 Rust + 🕸 Wasm = ❤");
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js: "10"
|
||||
|
||||
script:
|
||||
- ./node_modules/.bin/webpack
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Nym Sphinx Wasm Demo
|
||||
|
||||
This example application demonstrates how to use WebAssembly to create Sphinx packets, in the browser, and forward them to a Nym gateway.
|
||||
|
||||
## 🚴 Usage
|
||||
|
||||
```
|
||||
npm install # set up dependencies
|
||||
npm run start # starts a web server at http://localhost:8001
|
||||
```
|
||||
|
||||
Check your dev console for output.
|
||||
|
||||
### Rebuild after Rust source changes
|
||||
|
||||
Install `wasm-pack`. Instruction are at the [Rust WASM tutorial](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html).
|
||||
|
||||
`wasm-pack build` in the `clients/webassembly` directory (one up) will rebuild the wasm package if you make changes to the Rust source. That will be automatically picked up (and reloaded, if need be) by the npm dev server.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// A dependency graph that contains any wasm must all be imported
|
||||
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||
// that no one else needs to worry about it again.
|
||||
import("./index.js")
|
||||
.catch(e => console.error("Error importing `index.js`:", e));
|
||||
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nym WebAssembly Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>
|
||||
<label>Sender: </label><input disabled="true" size="85" type="text" id="sender" value="">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>Recipient: </label><input size="85" type="text" id="recipient" value="">
|
||||
</p>
|
||||
<p>
|
||||
<label>Message: </label><input type="text" id="message" value="Hello mixnet!">
|
||||
</p>
|
||||
<p>
|
||||
<button id="send-button">Send</button>
|
||||
</p>
|
||||
|
||||
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
|
||||
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
|
||||
messages show in green.</p>
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
<span id="output"></div>
|
||||
</p>
|
||||
<script src="./bootstrap.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {
|
||||
Client,
|
||||
Identity
|
||||
} from "@nymproject/nym-client-wasm/client"
|
||||
|
||||
async function main() {
|
||||
// Set up identity and client
|
||||
let directory = "https://directory.nymtech.net";
|
||||
let identity = new Identity();
|
||||
let nymClient = new Client(directory, identity, null);
|
||||
|
||||
// Wire up events callbacks
|
||||
nymClient.onConnect = (_) => displaySenderAddress(nymClient);
|
||||
nymClient.onText = displayReceived;
|
||||
nymClient.onErrorResponse = (event) => alert("Received invalid gateway response", event.data);
|
||||
const sendButton = document.querySelector('#send-button');
|
||||
sendButton.onclick = function () {
|
||||
sendMessageTo(nymClient);
|
||||
}
|
||||
|
||||
// Start the Nym client. Connects to a Nym gateway via websocket.
|
||||
await nymClient.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Sphinx packet and send it to the mixnet through the gateway node.
|
||||
*
|
||||
* Message and recipient are taken from the values in the user interface.
|
||||
*
|
||||
* @param {Client} nymClient the nym client to use for message sending
|
||||
*/
|
||||
function sendMessageTo(nymClient) {
|
||||
var message = document.getElementById("message").value;
|
||||
var recipient = document.getElementById("recipient").value;
|
||||
nymClient.sendMessage(message, recipient);
|
||||
displaySend(message);
|
||||
}
|
||||
/**
|
||||
* Display messages that have been sent up the websocket. Colours them blue.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
function displaySend(message) {
|
||||
let timestamp = new Date().toISOString().substr(11, 12);
|
||||
let out = "<p style='color: blue; word-break: break-all;'>" + timestamp + " <b>sent</b> >>> " + message + "</p >";
|
||||
document.getElementById("output").innerHTML = out + document.getElementById("output").innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display received text messages in the browser. Colour them green.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
function displayReceived(message) {
|
||||
let timestamp = new Date().toISOString().substr(11, 12);
|
||||
let out = "<p style='color: green; word-break: break-all;'>" + timestamp + " <b>received</b> >>> " + message + "</p >";
|
||||
document.getElementById("output").innerHTML = out + document.getElementById("output").innerHTML;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the nymClient's sender address in the user interface
|
||||
*
|
||||
* @param {Client} nymClient
|
||||
*/
|
||||
function displaySenderAddress(nymClient) {
|
||||
document.getElementById("sender").value = nymClient.formatAsRecipient();
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
+5410
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "create-wasm-app",
|
||||
"version": "0.1.0",
|
||||
"description": "create an app to consume rust-generated wasm packages",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"create-wasm-app": ".bin/create-wasm-app.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"start": "webpack-dev-server --port 8001"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
|
||||
},
|
||||
"keywords": [
|
||||
"webassembly",
|
||||
"wasm",
|
||||
"rust",
|
||||
"webpack"
|
||||
],
|
||||
"author": "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nymtech/nym/issues"
|
||||
},
|
||||
"homepage": "https://nymtech.net/docs",
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^5.0.0",
|
||||
"hello-wasm-pack": "^0.1.0",
|
||||
"webpack": "^4.29.3",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nymproject/nym-client-wasm": "^0.7.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: "./bootstrap.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "bootstrap.js",
|
||||
},
|
||||
mode: "development",
|
||||
plugins: [
|
||||
new CopyWebpackPlugin(['index.html'])
|
||||
],
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
Generated
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [
|
||||
"client.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"semver": "^7.3.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use crypto::identity::MixIdentityPublicKey;
|
||||
use models::Topology;
|
||||
use nymsphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx::Node as SphinxNode;
|
||||
use nymsphinx::{delays, Destination, NodeAddressBytes, SphinxPacket};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
mod models;
|
||||
mod utils;
|
||||
|
||||
pub use models::keys::keygen;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
|
||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
// allocator.
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct JsonRoute {
|
||||
nodes: Vec<NodeData>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct NodeData {
|
||||
address: String,
|
||||
public_key: String,
|
||||
}
|
||||
|
||||
/// Creates a Gateway payload for use in JavaScript applications, using wasm.
|
||||
/// It contains encoded address of first hop as well as the actual Sphinx Packet with the data.
|
||||
///
|
||||
/// The `wasm-pack build` command will cause this to output JS bindings and a
|
||||
/// wasm executable in the `pkg/` directory.
|
||||
///
|
||||
/// Message chunking is currently not implemented. If the message exceeds the
|
||||
/// capacity of a single Sphinx packet, the extra information will be discarded.
|
||||
#[wasm_bindgen]
|
||||
pub fn create_sphinx_packet(topology_json: &str, msg: &str, recipient: &str) -> Vec<u8> {
|
||||
utils::set_panic_hook(); // nicer js errors.
|
||||
|
||||
let recipient = Recipient::try_from_string(recipient).unwrap();
|
||||
|
||||
let route = sphinx_route_to(topology_json, &recipient.gateway());
|
||||
let average_delay = Duration::from_secs_f64(0.1);
|
||||
let delays = delays::generate_from_average_duration(route.len(), average_delay);
|
||||
|
||||
// TODO: once we are able to reconstruct split messages use this instead
|
||||
// let split_message = split_and_prepare_payloads(&msg.as_bytes());
|
||||
// assert_eq!(split_message.len(), 1);
|
||||
// let message = split_message.first().unwrap().clone();
|
||||
|
||||
let message = msg.as_bytes().to_vec();
|
||||
|
||||
let destination = Destination::new(recipient.destination(), Default::default());
|
||||
let sphinx_packet = SphinxPacket::new(message, &route, &destination, &delays, None).unwrap();
|
||||
payload(sphinx_packet, route)
|
||||
}
|
||||
|
||||
/// Concatenate the gateway address bytes with the Sphinx packet.
|
||||
///
|
||||
/// The Nym gateway node has no idea what is inside the Sphinx packet, or where
|
||||
/// it should send a packet it receives. So we prepend the packet with the
|
||||
/// address bytes of the first mix inside the packet, so that the gateway can
|
||||
/// forward the packet to it.
|
||||
fn payload(sphinx_packet: SphinxPacket, route: Vec<SphinxNode>) -> Vec<u8> {
|
||||
let packet = sphinx_packet.to_bytes();
|
||||
let first_node_address =
|
||||
NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone()).unwrap();
|
||||
|
||||
first_node_address
|
||||
.as_bytes()
|
||||
.into_iter()
|
||||
.chain(packet.into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Attempts to create a Sphinx route, which is a `Vec<sphinx::Node>`, from a
|
||||
/// JSON string.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the supplied `raw_route` json string can't be
|
||||
/// extracted to a `JsonRoute`.
|
||||
fn sphinx_route_to(topology_json: &str, gateway_address: &NodeAddressBytes) -> Vec<SphinxNode> {
|
||||
let topology = Topology::new(topology_json);
|
||||
let route = topology
|
||||
.random_route_to_gateway(gateway_address)
|
||||
.expect("invalid route produced");
|
||||
assert_eq!(4, route.len());
|
||||
route
|
||||
}
|
||||
|
||||
impl TryFrom<NodeData> for SphinxNode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(node_data: NodeData) -> Result<Self, Self::Error> {
|
||||
let addr: SocketAddr = node_data.address.parse().unwrap();
|
||||
let address: NodeAddressBytes = NymNodeRoutingAddress::from(addr).try_into().unwrap();
|
||||
|
||||
// this has to be temporarily moved out of separate function as we can't return private types
|
||||
let pub_key = {
|
||||
let src = MixIdentityPublicKey::from_base58_string(node_data.public_key).to_bytes();
|
||||
let mut dest: [u8; 32] = [0; 32];
|
||||
dest.copy_from_slice(&src);
|
||||
nymsphinx::key::new(dest)
|
||||
};
|
||||
|
||||
Ok(SphinxNode { address, pub_key })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_constructing_a_sphinx_packet {
|
||||
use super::*;
|
||||
|
||||
// the below test is no longer true, as the produced length is 1372 bytes + 7 (for IPV4) or + 19 (for IPV6)
|
||||
// conceptually everything works as before, only the 0 padding was removed as it served no purpose here
|
||||
|
||||
// #[test]
|
||||
// fn produces_1404_bytes() {
|
||||
// // 32 byte address + 1372 byte sphinx packet
|
||||
// let packet = create_gateway_sphinx_packet(
|
||||
// topology_fixture(),
|
||||
// "foomp",
|
||||
// "AetTDvynUNB2N35rvCVDxkPR593Cx4PCe4QQKrMgm5RR",
|
||||
// );
|
||||
// assert_eq!(1404, packet.len());
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn starts_with_a_mix_address() {
|
||||
let mut payload = create_sphinx_packet(
|
||||
topology_fixture(),
|
||||
"foomp",
|
||||
"5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq@7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
|
||||
);
|
||||
// you don't really need 32 bytes here, but giving too much won't make it fail
|
||||
let mut address_buffer = [0; 32];
|
||||
let _ = payload.split_off(32);
|
||||
address_buffer.copy_from_slice(payload.as_slice());
|
||||
let address = NymNodeRoutingAddress::try_from_bytes(&address_buffer);
|
||||
|
||||
assert!(address.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod building_a_topology_from_json {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_on_empty_string() {
|
||||
sphinx_route_to(
|
||||
"",
|
||||
&NodeAddressBytes::try_from_base58_string(
|
||||
"7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_on_bad_json() {
|
||||
sphinx_route_to(
|
||||
"bad bad bad not json",
|
||||
&NodeAddressBytes::try_from_base58_string(
|
||||
"7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_when_there_are_no_mixnodes() {
|
||||
let mut topology: Topology = serde_json::from_str(topology_fixture()).unwrap();
|
||||
topology.mix_nodes = vec![];
|
||||
let json = serde_json::to_string(&topology).unwrap();
|
||||
sphinx_route_to(
|
||||
&json,
|
||||
&NodeAddressBytes::try_from_base58_string(
|
||||
"7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_when_there_are_not_enough_mixnodes() {
|
||||
let mut topology: Topology = serde_json::from_str(topology_fixture()).unwrap();
|
||||
let node = topology.mix_nodes.first().unwrap().clone();
|
||||
topology.mix_nodes = vec![node]; // 1 mixnode isn't enough. Panic!
|
||||
let json = serde_json::to_string(&topology).unwrap();
|
||||
sphinx_route_to(
|
||||
&json,
|
||||
&NodeAddressBytes::try_from_base58_string(
|
||||
"7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_works_on_happy_json() {
|
||||
let route = sphinx_route_to(
|
||||
topology_fixture(),
|
||||
&NodeAddressBytes::try_from_base58_string(
|
||||
"7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(4, route.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn topology_fixture() -> &'static str {
|
||||
r#"
|
||||
{
|
||||
"cocoNodes": [],
|
||||
"mixNodes": [
|
||||
{
|
||||
"host": "nym.300baud.de:1789",
|
||||
"pubKey": "AetTDvynUNB2N35rvCVDxkPR593Cx4PCe4QQKrMgm5RR",
|
||||
"version": "0.6.0",
|
||||
"location": "Falkenstein, DE",
|
||||
"layer": 3,
|
||||
"lastSeen": 1587572945877713700
|
||||
},
|
||||
{
|
||||
"host": "testnet_nymmixnode.roussel-zeter.eu:1789",
|
||||
"pubKey": "9wJ3zLoyat41e4ZgT1AWeueExv5c6uwnjvkRepj8Ebis",
|
||||
"version": "0.6.0",
|
||||
"location": "Geneva, CH",
|
||||
"layer": 3,
|
||||
"lastSeen": 1587572945907250400
|
||||
},
|
||||
{
|
||||
"host": "185.144.83.134:1789",
|
||||
"pubKey": "59tCzpCYsiKXz89rtvNiEYwQDdkseSShPEkifQXhsCgA",
|
||||
"version": "0.6.0",
|
||||
"location": "Bucharest",
|
||||
"layer": 1,
|
||||
"lastSeen": 1587572946007431400
|
||||
},
|
||||
{
|
||||
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
|
||||
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
|
||||
"version": "0.6.0",
|
||||
"location": "Glarus",
|
||||
"layer": 1,
|
||||
"lastSeen": 1587572945920982000
|
||||
},
|
||||
{
|
||||
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
|
||||
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
|
||||
"version": "0.6.0",
|
||||
"location": "Glarus",
|
||||
"layer": 2,
|
||||
"lastSeen": 1587572945920982000
|
||||
},
|
||||
{
|
||||
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
|
||||
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
|
||||
"version": "0.6.0",
|
||||
"location": "Glarus",
|
||||
"layer": 2,
|
||||
"lastSeen": 1587572945920982000
|
||||
}
|
||||
],
|
||||
"mixProviderNodes":[],
|
||||
"gatewayNodes": [
|
||||
{
|
||||
"clientListener": "139.162.246.48:9000",
|
||||
"mixnetListener": "139.162.246.48:1789",
|
||||
"pubKey": "7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
|
||||
"version": "0.6.0",
|
||||
"location": "London, UK",
|
||||
"registeredClients": [
|
||||
{
|
||||
"pubKey": "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq"
|
||||
}
|
||||
],
|
||||
"lastSeen": 1587572946261865200
|
||||
},
|
||||
{
|
||||
"clientListener": "127.0.0.1:9000",
|
||||
"mixnetListener": "127.0.0.1:1789",
|
||||
"pubKey": "2XK8RDcUTRcJLUWoDfoXc2uP4YViscMLEM5NSzhSi87M",
|
||||
"version": "0.6.0",
|
||||
"location": "unknown",
|
||||
"registeredClients": [],
|
||||
"lastSeen": 1587572946304564700
|
||||
}
|
||||
]
|
||||
}
|
||||
"#
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topology::coco;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CocoPresence {
|
||||
pub location: String,
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::coco::Node> for CocoPresence {
|
||||
fn into(self) -> topology::coco::Node {
|
||||
topology::coco::Node {
|
||||
location: self.location,
|
||||
host: self.host,
|
||||
pub_key: self.pub_key,
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::coco::Node> for CocoPresence {
|
||||
fn from(cn: coco::Node) -> Self {
|
||||
CocoPresence {
|
||||
location: cn.location,
|
||||
host: cn.host,
|
||||
pub_key: cn.pub_key,
|
||||
last_seen: cn.last_seen,
|
||||
version: cn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topology::gateway;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GatewayPresence {
|
||||
pub location: String,
|
||||
pub client_listener: String,
|
||||
pub mixnet_listener: String,
|
||||
pub pub_key: String,
|
||||
pub registered_clients: Vec<GatewayClient>,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::gateway::Node> for GatewayPresence {
|
||||
fn into(self) -> topology::gateway::Node {
|
||||
topology::gateway::Node {
|
||||
location: self.location,
|
||||
client_listener: self.client_listener.parse().unwrap(),
|
||||
mixnet_listener: self.mixnet_listener.parse().unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
registered_clients: self
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::gateway::Node> for GatewayPresence {
|
||||
fn from(mpn: gateway::Node) -> Self {
|
||||
GatewayPresence {
|
||||
location: mpn.location,
|
||||
client_listener: mpn.client_listener.to_string(),
|
||||
mixnet_listener: mpn.mixnet_listener.to_string(),
|
||||
pub_key: mpn.pub_key,
|
||||
registered_clients: mpn
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: mpn.last_seen,
|
||||
version: mpn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GatewayClient {
|
||||
pub pub_key: String,
|
||||
}
|
||||
|
||||
impl Into<topology::gateway::Client> for GatewayClient {
|
||||
fn into(self) -> topology::gateway::Client {
|
||||
topology::gateway::Client {
|
||||
pub_key: self.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::gateway::Client> for GatewayClient {
|
||||
fn from(mpc: topology::gateway::Client) -> Self {
|
||||
GatewayClient {
|
||||
pub_key: mpc.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use crypto::identity::MixIdentityKeyPair;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GatewayIdentity {
|
||||
private_key: String,
|
||||
public_key: String,
|
||||
address: String,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for GatewayIdentity {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(msg: String) -> Result<Self, Self::Error> {
|
||||
serde_json::from_str(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<String> for GatewayIdentity {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_into(self) -> Result<String, Self::Error> {
|
||||
serde_json::to_string(&self)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn keygen() -> String {
|
||||
let keypair = MixIdentityKeyPair::new();
|
||||
let address = keypair.public_key().derive_address();
|
||||
|
||||
GatewayIdentity {
|
||||
private_key: keypair.private_key().to_base58_string(),
|
||||
public_key: keypair.public_key().to_base58_string(),
|
||||
address: address.to_base58_string(),
|
||||
}
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::net::ToSocketAddrs;
|
||||
use topology::mix;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixNodePresence {
|
||||
pub location: String,
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub layer: u64,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl TryInto<topology::mix::Node> for MixNodePresence {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_into(self) -> Result<topology::mix::Node, Self::Error> {
|
||||
let resolved_hostname = self.host.to_socket_addrs()?.next();
|
||||
if resolved_hostname.is_none() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"no valid socket address",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(topology::mix::Node {
|
||||
location: self.location,
|
||||
host: resolved_hostname.unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
layer: self.layer,
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::mix::Node> for MixNodePresence {
|
||||
fn from(mn: mix::Node) -> Self {
|
||||
MixNodePresence {
|
||||
location: mn.location,
|
||||
host: mn.host.to_string(),
|
||||
pub_key: mn.pub_key,
|
||||
layer: mn.layer,
|
||||
last_seen: mn.last_seen,
|
||||
version: mn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use nymsphinx::Node as SphinxNode;
|
||||
use nymsphinx::NodeAddressBytes;
|
||||
use rand::seq::IteratorRandom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use topology::{gateway, mix, provider};
|
||||
|
||||
pub mod coconodes;
|
||||
pub mod gateways;
|
||||
pub mod keys;
|
||||
pub mod mixnodes;
|
||||
pub mod providers;
|
||||
|
||||
// JS: can we just get rid of this? It's mostly just copied (and un-updated) code from NymTopology trait
|
||||
// Topology shows us the current state of the overall Nym network
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Topology {
|
||||
pub coco_nodes: Vec<coconodes::CocoPresence>,
|
||||
pub mix_nodes: Vec<mixnodes::MixNodePresence>,
|
||||
pub mix_provider_nodes: Vec<providers::MixProviderPresence>,
|
||||
pub gateway_nodes: Vec<gateways::GatewayPresence>,
|
||||
}
|
||||
|
||||
impl Topology {
|
||||
pub fn new(json: &str) -> Self {
|
||||
if json.is_empty() {
|
||||
panic!("empty json passed");
|
||||
}
|
||||
serde_json::from_str(json).unwrap()
|
||||
}
|
||||
|
||||
fn make_layered_topology(&self) -> Result<HashMap<u64, Vec<mix::Node>>, NymTopologyError> {
|
||||
let mut layered_topology: HashMap<u64, Vec<mix::Node>> = HashMap::new();
|
||||
let mut highest_layer = 0;
|
||||
for mix in self.mix_nodes() {
|
||||
// we need to have extra space for provider
|
||||
if mix.layer > nymsphinx::MAX_PATH_LENGTH as u64 {
|
||||
return Err(NymTopologyError::InvalidMixLayerError);
|
||||
}
|
||||
highest_layer = max(highest_layer, mix.layer);
|
||||
|
||||
let layer_nodes = layered_topology.entry(mix.layer).or_insert_with(Vec::new);
|
||||
layer_nodes.push(mix);
|
||||
}
|
||||
|
||||
// verify the topology - make sure there are no gaps and there is at least one node per layer
|
||||
let mut missing_layers = Vec::new();
|
||||
for layer in 1..=highest_layer {
|
||||
if !layered_topology.contains_key(&layer) {
|
||||
missing_layers.push(layer);
|
||||
}
|
||||
if layered_topology[&layer].is_empty() {
|
||||
missing_layers.push(layer);
|
||||
}
|
||||
}
|
||||
|
||||
if !missing_layers.is_empty() {
|
||||
return Err(NymTopologyError::MissingLayerError(missing_layers));
|
||||
}
|
||||
|
||||
Ok(layered_topology)
|
||||
}
|
||||
|
||||
// Tries to get a route through the mix network
|
||||
fn random_mix_route(&self) -> Result<Vec<SphinxNode>, NymTopologyError> {
|
||||
let mut layered_topology = self.make_layered_topology()?;
|
||||
let num_layers = layered_topology.len();
|
||||
let route = (1..=num_layers as u64)
|
||||
// unwrap is safe for 'remove' as it it failed, it implied the entry never existed
|
||||
// in the map in the first place which would contradict what we've just done
|
||||
.map(|layer| layered_topology.remove(&layer).unwrap()) // for each layer
|
||||
.map(|nodes| nodes.into_iter().choose(&mut rand::thread_rng()).unwrap()) // choose random node
|
||||
.map(|random_node| random_node.into()) // and convert it into sphinx specific node format
|
||||
.collect();
|
||||
|
||||
Ok(route)
|
||||
}
|
||||
|
||||
// Sets up a route to a specific gateway
|
||||
pub fn random_route_to_gateway(
|
||||
&self,
|
||||
gateway_address: &NodeAddressBytes,
|
||||
) -> Result<Vec<SphinxNode>, NymTopologyError> {
|
||||
let b58_address = gateway_address.to_base58_string();
|
||||
let gateway_node = self
|
||||
.gateways()
|
||||
.iter()
|
||||
.cloned()
|
||||
.find(|gateway| gateway.pub_key == b58_address.clone())
|
||||
.ok_or_else(|| NymTopologyError::InvalidMixLayerError)?;
|
||||
|
||||
Ok(self
|
||||
.random_mix_route()?
|
||||
.into_iter()
|
||||
.chain(std::iter::once(gateway_node.into()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn mix_nodes(&self) -> Vec<mix::Node> {
|
||||
self.mix_nodes
|
||||
.iter()
|
||||
.filter_map(|x| x.clone().try_into().ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// it's intesting how compiler knows this function is unused even though it's public
|
||||
// but it will be removed once our topology is refactored and the concept of "provider"
|
||||
// goes away.
|
||||
#[allow(dead_code)]
|
||||
pub fn providers(&self) -> Vec<provider::Node> {
|
||||
self.mix_provider_nodes
|
||||
.iter()
|
||||
.map(|x| x.clone().into())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn gateways(&self) -> Vec<gateway::Node> {
|
||||
self.gateway_nodes
|
||||
.iter()
|
||||
.map(|x| x.clone().into())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NymTopologyError {
|
||||
InvalidMixLayerError,
|
||||
MissingLayerError(Vec<u64>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod converting_mixnode_presence_into_topology_mixnode {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_returns_error_on_unresolvable_hostname() {
|
||||
let unresolvable_hostname = "foomp.foomp.foomp:1234";
|
||||
|
||||
let mix_presence = mixnodes::MixNodePresence {
|
||||
location: "".to_string(),
|
||||
host: unresolvable_hostname.to_string(),
|
||||
pub_key: "".to_string(),
|
||||
layer: 0,
|
||||
last_seen: 0,
|
||||
version: "".to_string(),
|
||||
};
|
||||
|
||||
let _result: Result<mix::Node, std::io::Error> = mix_presence.try_into();
|
||||
// assert!(result.is_err()) // This fails only for me. Why?
|
||||
// ¯\_(ツ)_/¯ - works on my machine (and travis)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_returns_resolved_ip_on_resolvable_hostname() {
|
||||
let resolvable_hostname = "nymtech.net:1234";
|
||||
|
||||
let mix_presence = mixnodes::MixNodePresence {
|
||||
location: "".to_string(),
|
||||
host: resolvable_hostname.to_string(),
|
||||
pub_key: "".to_string(),
|
||||
layer: 0,
|
||||
last_seen: 0,
|
||||
version: "".to_string(),
|
||||
};
|
||||
|
||||
let result: Result<topology::mix::Node, std::io::Error> = mix_presence.try_into();
|
||||
assert!(result.is_ok())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topology::provider;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderPresence {
|
||||
pub location: String,
|
||||
pub client_listener: String,
|
||||
pub mixnet_listener: String,
|
||||
pub pub_key: String,
|
||||
pub registered_clients: Vec<MixProviderClient>,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::provider::Node> for MixProviderPresence {
|
||||
fn into(self) -> topology::provider::Node {
|
||||
topology::provider::Node {
|
||||
location: self.location,
|
||||
client_listener: self.client_listener.parse().unwrap(),
|
||||
mixnet_listener: self.mixnet_listener.parse().unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
registered_clients: self
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::provider::Node> for MixProviderPresence {
|
||||
fn from(mpn: provider::Node) -> Self {
|
||||
MixProviderPresence {
|
||||
location: mpn.location,
|
||||
client_listener: mpn.client_listener.to_string(),
|
||||
mixnet_listener: mpn.mixnet_listener.to_string(),
|
||||
pub_key: mpn.pub_key,
|
||||
registered_clients: mpn
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: mpn.last_seen,
|
||||
version: mpn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderClient {
|
||||
pub pub_key: String,
|
||||
}
|
||||
|
||||
impl Into<topology::provider::Client> for MixProviderClient {
|
||||
fn into(self) -> topology::provider::Client {
|
||||
topology::provider::Client {
|
||||
pub_key: self.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::provider::Client> for MixProviderClient {
|
||||
fn from(mpc: topology::provider::Client) -> Self {
|
||||
MixProviderClient {
|
||||
pub_key: mpc.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "directory-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
futures = "0.3"
|
||||
pretty_env_logger = "0.3"
|
||||
reqwest = { version = "0.10", features = ["json"] }
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
|
||||
## internal
|
||||
topology = {path = "../../topology"}
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "0.23.0"
|
||||
tokio = { version = "0.2", features = ["macros"] }
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::requests::health_check_get::Request as HealthCheckRequest;
|
||||
use crate::requests::metrics_mixes_get::Request as MetricsMixRequest;
|
||||
use crate::requests::metrics_mixes_post::Request as MetricsMixPost;
|
||||
use crate::requests::presence_coconodes_post::Request as PresenceCocoNodesPost;
|
||||
use crate::requests::presence_gateways_post::Request as PresenceGatewayPost;
|
||||
use crate::requests::presence_mixnodes_post::Request as PresenceMixNodesPost;
|
||||
use crate::requests::presence_providers_post::Request as PresenceProvidersPost;
|
||||
use crate::requests::presence_topology_get::Request as PresenceTopologyRequest;
|
||||
|
||||
use metrics::{MixMetric, PersistedMixMetric};
|
||||
use presence::{
|
||||
coconodes::CocoPresence, gateways::GatewayPresence, mixnodes::MixNodePresence,
|
||||
providers::MixProviderPresence, Topology,
|
||||
};
|
||||
use requests::{health_check_get::HealthCheckResponse, DirectoryGetRequest, DirectoryPostRequest};
|
||||
|
||||
pub mod metrics;
|
||||
pub mod presence;
|
||||
pub mod requests;
|
||||
|
||||
pub struct Config {
|
||||
pub base_url: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(base_url: String) -> Self {
|
||||
Config { base_url }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DirectoryClient {
|
||||
fn new(config: Config) -> Self;
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
base_url: String,
|
||||
reqwest_client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl DirectoryClient for Client {
|
||||
fn new(config: Config) -> Client {
|
||||
let reqwest_client = reqwest::Client::new();
|
||||
Client {
|
||||
base_url: config.base_url,
|
||||
reqwest_client,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
async fn post<R: DirectoryPostRequest>(
|
||||
&self,
|
||||
request: R,
|
||||
) -> reqwest::Result<reqwest::Response> {
|
||||
self.reqwest_client
|
||||
.post(&request.url())
|
||||
.json(request.json_payload())
|
||||
.send()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get<R: DirectoryGetRequest>(&self, request: R) -> reqwest::Result<R::JSONResponse> {
|
||||
self.reqwest_client
|
||||
.get(&request.url())
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_healthcheck(&self) -> reqwest::Result<HealthCheckResponse> {
|
||||
let req = HealthCheckRequest::new(&self.base_url);
|
||||
self.get(req).await
|
||||
}
|
||||
|
||||
pub async fn post_mix_metrics(&self, metrics: MixMetric) -> reqwest::Result<reqwest::Response> {
|
||||
let req = MetricsMixPost::new(&self.base_url, metrics);
|
||||
self.post(req).await
|
||||
}
|
||||
|
||||
pub async fn get_mix_metrics(&self) -> reqwest::Result<Vec<PersistedMixMetric>> {
|
||||
let req = MetricsMixRequest::new(&self.base_url);
|
||||
self.get(req).await
|
||||
}
|
||||
|
||||
pub async fn post_coconode_presence(
|
||||
&self,
|
||||
presence: CocoPresence,
|
||||
) -> reqwest::Result<reqwest::Response> {
|
||||
let req = PresenceCocoNodesPost::new(&self.base_url, presence);
|
||||
self.post(req).await
|
||||
}
|
||||
|
||||
pub async fn post_gateway_presence(
|
||||
&self,
|
||||
presence: GatewayPresence,
|
||||
) -> reqwest::Result<reqwest::Response> {
|
||||
let req = PresenceGatewayPost::new(&self.base_url, presence);
|
||||
self.post(req).await
|
||||
}
|
||||
|
||||
pub async fn post_mixnode_presence(
|
||||
&self,
|
||||
presence: MixNodePresence,
|
||||
) -> reqwest::Result<reqwest::Response> {
|
||||
let req = PresenceMixNodesPost::new(&self.base_url, presence);
|
||||
self.post(req).await
|
||||
}
|
||||
|
||||
// this should be soft-deprecated as the whole concept of provider will
|
||||
// be removed in the next topology rework
|
||||
pub async fn post_provider_presence(
|
||||
&self,
|
||||
presence: MixProviderPresence,
|
||||
) -> reqwest::Result<reqwest::Response> {
|
||||
let req = PresenceProvidersPost::new(&self.base_url, presence);
|
||||
self.post(req).await
|
||||
}
|
||||
|
||||
pub async fn get_topology(&self) -> reqwest::Result<Topology> {
|
||||
let req = PresenceTopologyRequest::new(&self.base_url);
|
||||
self.get(req).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn client_test_fixture(base_url: &str) -> Client {
|
||||
Client {
|
||||
base_url: base_url.to_string(),
|
||||
reqwest_client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersistedMixMetric {
|
||||
pub pub_key: String,
|
||||
pub received: u64,
|
||||
pub sent: HashMap<String, u64>,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixMetric {
|
||||
pub pub_key: String,
|
||||
pub received: u64,
|
||||
pub sent: HashMap<String, u64>,
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topology::coco;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CocoPresence {
|
||||
pub location: String,
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::coco::Node> for CocoPresence {
|
||||
fn into(self) -> topology::coco::Node {
|
||||
topology::coco::Node {
|
||||
location: self.location,
|
||||
host: self.host,
|
||||
pub_key: self.pub_key,
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::coco::Node> for CocoPresence {
|
||||
fn from(cn: coco::Node) -> Self {
|
||||
CocoPresence {
|
||||
location: cn.location,
|
||||
host: cn.host,
|
||||
pub_key: cn.pub_key,
|
||||
last_seen: cn.last_seen,
|
||||
version: cn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topology::gateway;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GatewayPresence {
|
||||
pub location: String,
|
||||
pub client_listener: String,
|
||||
pub mixnet_listener: String,
|
||||
pub pub_key: String,
|
||||
pub registered_clients: Vec<GatewayClient>,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::gateway::Node> for GatewayPresence {
|
||||
fn into(self) -> topology::gateway::Node {
|
||||
topology::gateway::Node {
|
||||
location: self.location,
|
||||
client_listener: self.client_listener.parse().unwrap(),
|
||||
mixnet_listener: self.mixnet_listener.parse().unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
registered_clients: self
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::gateway::Node> for GatewayPresence {
|
||||
fn from(mpn: gateway::Node) -> Self {
|
||||
GatewayPresence {
|
||||
location: mpn.location,
|
||||
client_listener: mpn.client_listener.to_string(),
|
||||
mixnet_listener: mpn.mixnet_listener.to_string(),
|
||||
pub_key: mpn.pub_key,
|
||||
registered_clients: mpn
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: mpn.last_seen,
|
||||
version: mpn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GatewayClient {
|
||||
pub pub_key: String,
|
||||
}
|
||||
|
||||
impl Into<topology::gateway::Client> for GatewayClient {
|
||||
fn into(self) -> topology::gateway::Client {
|
||||
topology::gateway::Client {
|
||||
pub_key: self.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::gateway::Client> for GatewayClient {
|
||||
fn from(mpc: topology::gateway::Client) -> Self {
|
||||
GatewayClient {
|
||||
pub_key: mpc.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::net::ToSocketAddrs;
|
||||
use topology::mix;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixNodePresence {
|
||||
pub location: String,
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub layer: u64,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl TryInto<topology::mix::Node> for MixNodePresence {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_into(self) -> Result<topology::mix::Node, Self::Error> {
|
||||
let resolved_hostname = self.host.to_socket_addrs()?.next();
|
||||
if resolved_hostname.is_none() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"no valid socket address",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(topology::mix::Node {
|
||||
location: self.location,
|
||||
host: resolved_hostname.unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
layer: self.layer,
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::mix::Node> for MixNodePresence {
|
||||
fn from(mn: mix::Node) -> Self {
|
||||
MixNodePresence {
|
||||
location: mn.location,
|
||||
host: mn.host.to_string(),
|
||||
pub_key: mn.pub_key,
|
||||
layer: mn.layer,
|
||||
last_seen: mn.last_seen,
|
||||
version: mn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod coconodes;
|
||||
pub mod gateways;
|
||||
pub mod mixnodes;
|
||||
pub mod providers;
|
||||
pub mod topology;
|
||||
|
||||
pub use self::topology::Topology;
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topology::provider;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderPresence {
|
||||
pub location: String,
|
||||
pub client_listener: String,
|
||||
pub mixnet_listener: String,
|
||||
pub pub_key: String,
|
||||
pub registered_clients: Vec<MixProviderClient>,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::provider::Node> for MixProviderPresence {
|
||||
fn into(self) -> topology::provider::Node {
|
||||
topology::provider::Node {
|
||||
location: self.location,
|
||||
client_listener: self.client_listener.parse().unwrap(),
|
||||
mixnet_listener: self.mixnet_listener.parse().unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
registered_clients: self
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::provider::Node> for MixProviderPresence {
|
||||
fn from(mpn: provider::Node) -> Self {
|
||||
MixProviderPresence {
|
||||
location: mpn.location,
|
||||
client_listener: mpn.client_listener.to_string(),
|
||||
mixnet_listener: mpn.mixnet_listener.to_string(),
|
||||
pub_key: mpn.pub_key,
|
||||
registered_clients: mpn
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: mpn.last_seen,
|
||||
version: mpn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderClient {
|
||||
pub pub_key: String,
|
||||
}
|
||||
|
||||
impl Into<topology::provider::Client> for MixProviderClient {
|
||||
fn into(self) -> topology::provider::Client {
|
||||
topology::provider::Client {
|
||||
pub_key: self.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::provider::Client> for MixProviderClient {
|
||||
fn from(mpc: topology::provider::Client) -> Self {
|
||||
MixProviderClient {
|
||||
pub_key: mpc.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{coconodes, gateways, mixnodes, providers};
|
||||
use crate::{Client, Config, DirectoryClient};
|
||||
use futures::future::BoxFuture;
|
||||
use log::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use topology::{coco, gateway, mix, provider, NymTopology};
|
||||
// Topology shows us the current state of the overall Nym network
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Topology {
|
||||
pub coco_nodes: Vec<coconodes::CocoPresence>,
|
||||
pub mix_nodes: Vec<mixnodes::MixNodePresence>,
|
||||
pub mix_provider_nodes: Vec<providers::MixProviderPresence>,
|
||||
pub gateway_nodes: Vec<gateways::GatewayPresence>,
|
||||
}
|
||||
|
||||
impl NymTopology for Topology {
|
||||
// TODO: this will need some changes to not imply having to make an HTTP request in constructor
|
||||
// TODO2: and also be reworked/removed with the topology re-work
|
||||
fn new<'a>(directory_server: String) -> BoxFuture<'a, Self> {
|
||||
debug!("Using directory server: {:?}", directory_server);
|
||||
let directory_config = Config {
|
||||
base_url: directory_server,
|
||||
};
|
||||
let directory = Client::new(directory_config);
|
||||
Box::pin({
|
||||
async move {
|
||||
directory
|
||||
.get_topology()
|
||||
.await
|
||||
.expect("Failed to retrieve network topology.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn new_from_nodes(
|
||||
mix_nodes: Vec<mix::Node>,
|
||||
mix_provider_nodes: Vec<provider::Node>,
|
||||
coco_nodes: Vec<coco::Node>,
|
||||
gateway_nodes: Vec<gateway::Node>,
|
||||
) -> Self {
|
||||
Topology {
|
||||
coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(),
|
||||
mix_nodes: mix_nodes.into_iter().map(|node| node.into()).collect(),
|
||||
mix_provider_nodes: mix_provider_nodes
|
||||
.into_iter()
|
||||
.map(|node| node.into())
|
||||
.collect(),
|
||||
gateway_nodes: gateway_nodes.into_iter().map(|node| node.into()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn mix_nodes(&self) -> Vec<mix::Node> {
|
||||
self.mix_nodes
|
||||
.iter()
|
||||
.filter_map(|x| x.clone().try_into().ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn providers(&self) -> Vec<provider::Node> {
|
||||
self.mix_provider_nodes
|
||||
.iter()
|
||||
.map(|x| x.clone().into())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn gateways(&self) -> Vec<gateway::Node> {
|
||||
self.gateway_nodes
|
||||
.iter()
|
||||
.map(|x| x.clone().into())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn coco_nodes(&self) -> Vec<topology::coco::Node> {
|
||||
self.coco_nodes.iter().map(|x| x.clone().into()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod converting_mixnode_presence_into_topology_mixnode {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_returns_error_on_unresolvable_hostname() {
|
||||
let unresolvable_hostname = "foomp.foomp.foomp:1234";
|
||||
|
||||
let mix_presence = mixnodes::MixNodePresence {
|
||||
location: "".to_string(),
|
||||
host: unresolvable_hostname.to_string(),
|
||||
pub_key: "".to_string(),
|
||||
layer: 0,
|
||||
last_seen: 0,
|
||||
version: "".to_string(),
|
||||
};
|
||||
|
||||
let result: Result<mix::Node, std::io::Error> = mix_presence.try_into();
|
||||
assert!(result.is_err()) // This fails only for me. Why?
|
||||
// ¯\_(ツ)_/¯ - works on my machine (and travis)
|
||||
// Is it still broken?
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_returns_resolved_ip_on_resolvable_hostname() {
|
||||
let resolvable_hostname = "nymtech.net:1234";
|
||||
|
||||
let mix_presence = mixnodes::MixNodePresence {
|
||||
location: "".to_string(),
|
||||
host: resolvable_hostname.to_string(),
|
||||
pub_key: "".to_string(),
|
||||
layer: 0,
|
||||
last_seen: 0,
|
||||
version: "".to_string(),
|
||||
};
|
||||
|
||||
let result: Result<topology::mix::Node, std::io::Error> = mix_presence.try_into();
|
||||
assert!(result.is_ok())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{DirectoryGetRequest, DirectoryRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const PATH: &str = "/api/healthcheck";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct HealthCheckResponse {
|
||||
pub ok: bool,
|
||||
}
|
||||
|
||||
pub struct Request {
|
||||
base_url: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl DirectoryRequest for Request {
|
||||
fn url(&self) -> String {
|
||||
format!("{}{}", self.base_url, self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectoryGetRequest for Request {
|
||||
type JSONResponse = HealthCheckResponse;
|
||||
|
||||
fn new(base_url: &str) -> Self {
|
||||
Request {
|
||||
base_url: base_url.to_string(),
|
||||
path: PATH.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod healthcheck_requests {
|
||||
use crate::client_test_fixture;
|
||||
use mockito::mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_400_status {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_returns_an_error() {
|
||||
let _m = mock("GET", "/api/healthcheck").with_status(400).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let res = client.get_healthcheck().await;
|
||||
assert!(res.is_err());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_200 {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_returns_a_response_with_200_status() {
|
||||
let json = r#"{
|
||||
"ok": true
|
||||
}"#;
|
||||
let _m = mock("GET", "/api/healthcheck")
|
||||
.with_status(200)
|
||||
.with_body(json)
|
||||
.create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let res = client.get_healthcheck().await;
|
||||
assert!(res.unwrap().ok);
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
-38
@@ -1,47 +1,60 @@
|
||||
use crate::clients::directory::metrics::PersistedMixMetric;
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{DirectoryGetRequest, DirectoryRequest};
|
||||
use crate::metrics::PersistedMixMetric;
|
||||
|
||||
const PATH: &str = "/api/metrics/mixes";
|
||||
|
||||
pub struct Request {
|
||||
base_url: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
pub trait MetricsMixRequester {
|
||||
fn new(base_url: String) -> Self;
|
||||
fn get(&self) -> Result<Vec<PersistedMixMetric>, reqwest::Error>;
|
||||
impl DirectoryRequest for Request {
|
||||
fn url(&self) -> String {
|
||||
format!("{}{}", self.base_url, self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl MetricsMixRequester for Request {
|
||||
fn new(base_url: String) -> Self {
|
||||
Request {
|
||||
base_url,
|
||||
path: "/api/metrics/mixes".to_string(),
|
||||
}
|
||||
}
|
||||
impl DirectoryGetRequest for Request {
|
||||
type JSONResponse = Vec<PersistedMixMetric>;
|
||||
|
||||
fn get(&self) -> Result<Vec<PersistedMixMetric>, reqwest::Error> {
|
||||
let url = format!("{}{}", self.base_url, self.path);
|
||||
let mix_metric_vec = reqwest::get(&url)?.json()?;
|
||||
Ok(mix_metric_vec)
|
||||
fn new(base_url: &str) -> Self {
|
||||
Request {
|
||||
base_url: base_url.to_string(),
|
||||
path: PATH.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod metrics_get_request {
|
||||
use super::*;
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::client_test_fixture;
|
||||
use mockito::mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_400_status {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn it_returns_an_error() {
|
||||
let _m = mock("GET", "/api/metrics/mixes").with_status(400).create();
|
||||
let req = Request::new(mockito::server_url());
|
||||
req.get().unwrap();
|
||||
#[tokio::test]
|
||||
async fn it_returns_an_error() {
|
||||
let _m = mock("GET", PATH).with_status(400).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let result = client.get_mix_metrics().await;
|
||||
assert!(result.is_err());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
@@ -49,20 +62,15 @@ mod metrics_get_request {
|
||||
#[cfg(test)]
|
||||
mod on_a_200 {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn it_returns_a_response_with_200_status_and_a_correct_topology() {
|
||||
#[tokio::test]
|
||||
async fn it_returns_a_response_with_200_status_and_a_correct_metrics() {
|
||||
let json = fixtures::mix_metrics_response_json();
|
||||
let _m = mock("GET", "/api/metrics/mixes")
|
||||
.with_status(200)
|
||||
.with_body(json)
|
||||
.create();
|
||||
let req = Request::new(mockito::server_url());
|
||||
let result = req.get();
|
||||
assert_eq!(true, result.is_ok());
|
||||
assert_eq!(
|
||||
1576061080635800000,
|
||||
result.unwrap().first().unwrap().timestamp
|
||||
);
|
||||
let _m = mock("GET", PATH).with_status(200).with_body(json).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let result = client.get_mix_metrics().await;
|
||||
let unwrapped = result.unwrap();
|
||||
|
||||
assert_eq!(10, unwrapped.first().unwrap().received);
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
@@ -70,7 +78,7 @@ mod metrics_get_request {
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
#[cfg(test)]
|
||||
pub fn mix_metrics_response_json() -> String {
|
||||
pub fn mix_metrics_response_json() -> &'static str {
|
||||
r#"[
|
||||
{
|
||||
"pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=",
|
||||
@@ -233,7 +241,6 @@ mod metrics_get_request {
|
||||
"timestamp": 1576061080501732900
|
||||
}
|
||||
]"#
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{DirectoryPostRequest, DirectoryRequest};
|
||||
use crate::metrics::MixMetric;
|
||||
|
||||
const PATH: &str = "/api/metrics/mixes";
|
||||
|
||||
pub struct Request {
|
||||
base_url: String,
|
||||
path: String,
|
||||
payload: MixMetric,
|
||||
}
|
||||
|
||||
impl DirectoryRequest for Request {
|
||||
fn url(&self) -> String {
|
||||
format!("{}{}", self.base_url, self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectoryPostRequest for Request {
|
||||
type Payload = MixMetric;
|
||||
fn json_payload(&self) -> &MixMetric {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
fn new(base_url: &str, payload: Self::Payload) -> Self {
|
||||
Request {
|
||||
base_url: base_url.to_string(),
|
||||
path: PATH.to_string(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod metrics_post_request {
|
||||
use super::*;
|
||||
use crate::client_test_fixture;
|
||||
use mockito::mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_400_status {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_returns_an_error() {
|
||||
let _m = mock("POST", PATH).with_status(400).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let result = client.post_mix_metrics(fixtures::new_metric()).await;
|
||||
assert_eq!(400, result.unwrap().status());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_200 {
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn it_returns_a_response_with_200() {
|
||||
let json = fixtures::mix_metrics_response_json();
|
||||
let _m = mock("POST", "/api/metrics/mixes")
|
||||
.with_status(201)
|
||||
.with_body(json)
|
||||
.create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let result = client.post_mix_metrics(fixtures::new_metric()).await;
|
||||
assert!(result.is_ok());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
use crate::metrics::MixMetric;
|
||||
|
||||
pub fn new_metric() -> MixMetric {
|
||||
MixMetric {
|
||||
pub_key: "abc".to_string(),
|
||||
received: 666,
|
||||
sent: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn mix_metrics_response_json() -> String {
|
||||
r#"
|
||||
{
|
||||
"pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=",
|
||||
"sent": {
|
||||
"35.178.213.77:1789": 1,
|
||||
"52.56.99.196:1789": 2
|
||||
},
|
||||
"received": 10,
|
||||
"timestamp": 1576061080635800000
|
||||
}
|
||||
"#
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod health_check_get;
|
||||
pub mod metrics_mixes_get;
|
||||
pub mod metrics_mixes_post;
|
||||
pub mod presence_coconodes_post;
|
||||
pub mod presence_gateways_post;
|
||||
pub mod presence_mixnodes_post;
|
||||
pub mod presence_providers_post;
|
||||
pub mod presence_topology_get;
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
pub(crate) trait DirectoryRequest {
|
||||
fn url(&self) -> String;
|
||||
}
|
||||
|
||||
pub(crate) trait DirectoryGetRequest: DirectoryRequest {
|
||||
// perhaps the name of this is not the best because it's technically not a JSON,
|
||||
// but something that can be deserialised from JSON.
|
||||
// I'm open to all suggestions on how to rename it
|
||||
type JSONResponse: DeserializeOwned;
|
||||
|
||||
fn new(base_url: &str) -> Self;
|
||||
}
|
||||
|
||||
pub(crate) trait DirectoryPostRequest: DirectoryRequest {
|
||||
// Similarly this, it's something that can be serialized into a JSON
|
||||
type Payload: Serialize + ?Sized;
|
||||
|
||||
fn new(base_url: &str, payload: Self::Payload) -> Self;
|
||||
fn json_payload(&self) -> &Self::Payload;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{DirectoryPostRequest, DirectoryRequest};
|
||||
use crate::presence::coconodes::CocoPresence;
|
||||
|
||||
const PATH: &str = "/api/presence/coconodes";
|
||||
|
||||
pub struct Request {
|
||||
base_url: String,
|
||||
path: String,
|
||||
payload: CocoPresence,
|
||||
}
|
||||
|
||||
impl DirectoryRequest for Request {
|
||||
fn url(&self) -> String {
|
||||
format!("{}{}", self.base_url, self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectoryPostRequest for Request {
|
||||
type Payload = CocoPresence;
|
||||
fn json_payload(&self) -> &CocoPresence {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
fn new(base_url: &str, payload: Self::Payload) -> Self {
|
||||
Request {
|
||||
base_url: base_url.to_string(),
|
||||
path: PATH.to_string(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod presence_coconodes_post_request {
|
||||
use super::*;
|
||||
use crate::client_test_fixture;
|
||||
use mockito::mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_400_status {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_returns_an_error() {
|
||||
let _m = mock("POST", PATH).with_status(400).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let presence = fixtures::new_presence();
|
||||
let result = client.post_coconode_presence(presence).await;
|
||||
assert_eq!(400, result.unwrap().status());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_200 {
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn it_returns_a_response_with_201() {
|
||||
let json = r#"{
|
||||
"ok": true
|
||||
}"#;
|
||||
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let presence = fixtures::new_presence();
|
||||
let result = client.post_coconode_presence(presence).await;
|
||||
assert!(result.is_ok());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
use crate::presence::coconodes::CocoPresence;
|
||||
|
||||
pub fn new_presence() -> CocoPresence {
|
||||
CocoPresence {
|
||||
location: "foomp".to_string(),
|
||||
host: "foo.com".to_string(),
|
||||
pub_key: "abc".to_string(),
|
||||
last_seen: 666,
|
||||
version: "0.2.0".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{DirectoryPostRequest, DirectoryRequest};
|
||||
use crate::presence::gateways::GatewayPresence;
|
||||
|
||||
const PATH: &str = "/api/presence/gateways";
|
||||
|
||||
pub struct Request {
|
||||
base_url: String,
|
||||
path: String,
|
||||
payload: GatewayPresence,
|
||||
}
|
||||
|
||||
impl DirectoryRequest for Request {
|
||||
fn url(&self) -> String {
|
||||
format!("{}{}", self.base_url, self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectoryPostRequest for Request {
|
||||
type Payload = GatewayPresence;
|
||||
fn json_payload(&self) -> &GatewayPresence {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
fn new(base_url: &str, payload: Self::Payload) -> Self {
|
||||
Request {
|
||||
base_url: base_url.to_string(),
|
||||
path: PATH.to_string(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod presence_gateways_post_request {
|
||||
use super::*;
|
||||
use crate::client_test_fixture;
|
||||
use mockito::mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_400_status {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_returns_an_error() {
|
||||
let _m = mock("POST", PATH).with_status(400).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let presence = fixtures::new_presence();
|
||||
let result = client.post_gateway_presence(presence).await;
|
||||
assert_eq!(400, result.unwrap().status());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_200 {
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn it_returns_a_response_with_201() {
|
||||
let json = r#"{
|
||||
"ok": true
|
||||
}"#;
|
||||
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let presence = fixtures::new_presence();
|
||||
let result = client.post_gateway_presence(presence).await;
|
||||
assert!(result.is_ok());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
use crate::presence::gateways::GatewayPresence;
|
||||
|
||||
pub fn new_presence() -> GatewayPresence {
|
||||
GatewayPresence {
|
||||
location: "foomp".to_string(),
|
||||
client_listener: "foo.com".to_string(),
|
||||
mixnet_listener: "foo.com".to_string(),
|
||||
pub_key: "abc".to_string(),
|
||||
registered_clients: vec![],
|
||||
last_seen: 0,
|
||||
version: "0.1.0".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{DirectoryPostRequest, DirectoryRequest};
|
||||
use crate::presence::mixnodes::MixNodePresence;
|
||||
|
||||
const PATH: &str = "/api/presence/mixnodes";
|
||||
|
||||
pub struct Request {
|
||||
base_url: String,
|
||||
path: String,
|
||||
payload: MixNodePresence,
|
||||
}
|
||||
|
||||
impl DirectoryRequest for Request {
|
||||
fn url(&self) -> String {
|
||||
format!("{}{}", self.base_url, self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectoryPostRequest for Request {
|
||||
type Payload = MixNodePresence;
|
||||
fn json_payload(&self) -> &MixNodePresence {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
fn new(base_url: &str, payload: Self::Payload) -> Self {
|
||||
Request {
|
||||
base_url: base_url.to_string(),
|
||||
path: PATH.to_string(),
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod presence_mixnodes_post_request {
|
||||
use super::*;
|
||||
use crate::client_test_fixture;
|
||||
use mockito::mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_400_status {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_returns_an_error() {
|
||||
let _m = mock("POST", PATH).with_status(400).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let presence = fixtures::new_presence();
|
||||
let result = client.post_mixnode_presence(presence).await;
|
||||
assert_eq!(400, result.unwrap().status());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod on_a_200 {
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn it_returns_a_response_with_201() {
|
||||
let json = r#"{
|
||||
"ok": true
|
||||
}"#;
|
||||
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
|
||||
let client = client_test_fixture(&mockito::server_url());
|
||||
let presence = fixtures::new_presence();
|
||||
let result = client.post_mixnode_presence(presence).await;
|
||||
assert!(result.is_ok());
|
||||
_m.assert();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
use crate::presence::mixnodes::MixNodePresence;
|
||||
|
||||
pub fn new_presence() -> MixNodePresence {
|
||||
MixNodePresence {
|
||||
location: "foomp".to_string(),
|
||||
host: "foo.com".to_string(),
|
||||
pub_key: "abc".to_string(),
|
||||
layer: 1,
|
||||
last_seen: 0,
|
||||
version: "0.1.0".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user