mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
1008 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fa2e6164f | |||
| 9972d9607e | |||
| 6a72194106 | |||
| 254e6164d7 | |||
| 675386241b | |||
| 8671acc1a6 | |||
| fb08bb6bd4 | |||
| 70f8e5ab3a | |||
| e53a8d76f9 | |||
| e0b4551bca | |||
| 7b9757bf3b | |||
| 4156b83b84 | |||
| f829b8abe0 | |||
| 838e0bd6b0 | |||
| c3f6906309 | |||
| 5cafd1ebe4 | |||
| 51991324a9 | |||
| 33a8a51962 | |||
| 40eaa54cf0 | |||
| a3bfa6f1ab | |||
| 7778ff63b5 | |||
| 79c5eb41c3 | |||
| 9e30c7d2e9 | |||
| d80129ba46 | |||
| 6907663f07 | |||
| dca96a52b4 | |||
| cf4fca8086 | |||
| 318f36a205 | |||
| 87aa73cc64 | |||
| 4e28d57aee | |||
| 6d67a3d5f5 | |||
| 1b5f4d6dfd | |||
| bed2f2e5d6 | |||
| 7d91666860 | |||
| 8800f861ed | |||
| 99336f6552 | |||
| 0b7b65296b | |||
| e2f931b4cb | |||
| d6ad7d11d0 | |||
| 914917036e | |||
| 6e9ca435ca | |||
| 0797e99559 | |||
| 0ba064ff48 | |||
| b4fe9efadb | |||
| b1b08ce716 | |||
| 20480992c4 | |||
| 9df90b259a | |||
| b94a412c09 | |||
| 0b0a553123 | |||
| 6d4d2f4ebf | |||
| e55358f685 | |||
| c2fdd0ca93 | |||
| 19044b0f53 | |||
| d4e77e6d57 | |||
| 6ba3e4f001 | |||
| 8152191277 | |||
| 8c6c909e98 | |||
| fdda87fd31 | |||
| 3907f6a4c1 | |||
| 652971d746 | |||
| 1bf8d7e4c4 | |||
| 3c834ac8e3 | |||
| 8669ae78e1 | |||
| 53f8c8dbc4 | |||
| 1328660b0b | |||
| 997edf4e4c | |||
| beb40ad09c | |||
| 4bac284083 | |||
| eae9216fe4 | |||
| dc1860c74d | |||
| 6209d3ee3d | |||
| 33aded09c6 | |||
| f360034f29 | |||
| 01e72064d4 | |||
| 91a75b681d | |||
| 0efdf0cdd7 | |||
| 75efd13556 | |||
| cd812b03cd | |||
| 794a2a1925 | |||
| 75899594c3 | |||
| 4944762d7c | |||
| d6431db248 | |||
| b8f649d1cb | |||
| 9fa8ca8c77 | |||
| 59da677c23 | |||
| 18a06b0a51 | |||
| 5e844bed5e | |||
| 46bf98d7f6 | |||
| 3e9ddd8720 | |||
| 9e33d9e56b | |||
| 9fae327315 | |||
| a6f5eb0dd2 | |||
| c3c4cd05b1 | |||
| 58c19385a3 | |||
| 354e1c0ed9 | |||
| 02b9677a9c | |||
| 82d26e61da | |||
| a113770868 | |||
| b734f44200 | |||
| ee16d1329b | |||
| 900654e6b1 | |||
| a0cba19645 | |||
| d7ccbd92e5 | |||
| 2da80e8dee | |||
| 450ef8ce1c | |||
| 58e7b7a069 | |||
| d354831c6a | |||
| fcb7caed23 | |||
| c7d1c4baba | |||
| 76423ce868 | |||
| b37006ce2f | |||
| b072130e37 | |||
| 54dfa7bdd7 | |||
| e5a75307c2 | |||
| 4fcb8c4792 | |||
| fb05b358c0 | |||
| f13624c58f | |||
| 497a60cc90 | |||
| 8139e60094 | |||
| 9dab67f3f1 | |||
| e342c9a28f | |||
| cf943b7119 | |||
| b7295cd56f | |||
| 763d9a9f17 | |||
| f3107e349c | |||
| caf2cded6e | |||
| b44380c81a | |||
| 35c8c10b7e | |||
| 91227e445a | |||
| 94e6d99dc7 | |||
| 052ff060b1 | |||
| e843fccbc6 | |||
| 3ab1f94514 | |||
| 4aa4843b74 | |||
| 4d00c334b1 | |||
| 62986b5cad | |||
| 296295ab8a | |||
| d1237e2718 | |||
| a26b7c594a | |||
| 853e86e6d0 | |||
| a386e3a3d2 | |||
| 560d9cf967 | |||
| 713947e432 | |||
| f8690ca215 | |||
| 4f26156030 | |||
| b353ed8ae9 | |||
| f269fb175d | |||
| 98fe4438d5 | |||
| 0459d6d9f4 | |||
| 4ff564521f | |||
| 9f139134c2 | |||
| 284ad03ebf | |||
| c3693b3e66 | |||
| 960bc99f5e | |||
| 8d33b648cc | |||
| 38cb184cf8 | |||
| 5d14caf3b2 | |||
| 81c571f0c7 | |||
| 50b21e95c3 | |||
| 059bf88141 | |||
| b3112ab0bd | |||
| 1bb8a10633 | |||
| f3230fc8f9 | |||
| c762bc3120 | |||
| aeb8903ba2 | |||
| 2f244771a2 | |||
| 5ffb1d0c36 | |||
| 62910caae1 | |||
| 866fd35df5 | |||
| 32a2ebb66f | |||
| 70d9a87d0d | |||
| 0bd39309c8 | |||
| 751ce54fa5 | |||
| 72dc677819 | |||
| 7002367e4f | |||
| 5339e48251 | |||
| d626e23210 | |||
| 342ed5e259 | |||
| 2bf09f8e2d | |||
| 36d93a8e1b | |||
| 1d7d306950 | |||
| 06306c669d | |||
| d19e126e2e | |||
| bf84e26a86 | |||
| a03ad51622 | |||
| 447b06ec2c | |||
| 6da1d5ed01 | |||
| 836c367499 | |||
| 15cb99a41f | |||
| fae74dd3ca | |||
| e462ae270d | |||
| 24d5a28a4a | |||
| 3df42898ae | |||
| 1701e4fde4 | |||
| f6d175b774 | |||
| 878c450563 | |||
| b444ea064e | |||
| 4eb90266a2 | |||
| 3935a369b8 | |||
| 32fc760053 | |||
| 5459c6d531 | |||
| a1889b801c | |||
| a86dff9433 | |||
| 02f59b6b27 | |||
| 29bb93cdf7 | |||
| eae06aade7 | |||
| a8b2fc15de | |||
| f699d9cf9a | |||
| ae00e8ce6e | |||
| 2730e75474 | |||
| 00e0224fd1 | |||
| 82c9b75ea1 | |||
| a23c57bab0 | |||
| 02b92c178a | |||
| 8dd6c3e35e | |||
| 78f2fda6ca | |||
| 2f822aa1f6 | |||
| 40f2f073cd | |||
| 37fabf0229 | |||
| fe61522f2a | |||
| 24ded8070f | |||
| 24569a4c49 | |||
| b448d605f0 | |||
| 0dcfde35f4 | |||
| 6acc1125b8 | |||
| 94ecb13d2f | |||
| 6008efcd95 | |||
| d6a8e9d4dc | |||
| bcd8c33005 | |||
| e71c84a785 | |||
| 3474849b1d | |||
| f32c5cc222 | |||
| cf6e1490f4 | |||
| 86951f509c | |||
| 8e9b2c6f05 | |||
| 9ace8dd928 | |||
| 8f727903f3 | |||
| b6aa09bc19 | |||
| 782550318a | |||
| d02543639b | |||
| e1e16cdf3b | |||
| 1bbcf83f43 | |||
| 169bd408f2 | |||
| 777e3b71ba | |||
| 511e54764e | |||
| d0b5fb5107 | |||
| 7c3137e2c8 | |||
| 12a3ec28bc | |||
| 892295c42a | |||
| ea9ef4af24 | |||
| b8870f3599 | |||
| 11301617d2 | |||
| 617be576f8 | |||
| 8523979c9c | |||
| 8768acee68 | |||
| a73d2121e1 | |||
| dd58fad69e | |||
| ba9d689ef9 | |||
| 97d82740d0 | |||
| 0750c29c7f | |||
| 639ee6b5b2 | |||
| f764f2aaa9 | |||
| d2a9c56d9c | |||
| 4c3289aa52 | |||
| 79376bca64 | |||
| b709d36c01 | |||
| 0301c85e2c | |||
| 7e979817e2 | |||
| 13cc4436ca | |||
| bcfbf37ed1 | |||
| 74ff870cf5 | |||
| b4f54e8958 | |||
| d179a3af15 | |||
| 2bac2feeb5 | |||
| 261236c8ce | |||
| 4a7969ef24 | |||
| f670ff22c6 | |||
| cb3194f10a | |||
| f6b2bbf433 | |||
| 56a4862442 | |||
| 20390d65c8 | |||
| 177457e67b | |||
| 8caec4d739 | |||
| fb819b99a4 | |||
| fe82037f06 | |||
| c2aa1b21f8 | |||
| d69415b55a | |||
| 4fae2d1255 | |||
| a300f269f1 | |||
| 961cba3f7c | |||
| 6bc7386bff | |||
| 5fa08f286e | |||
| 35f868733c | |||
| e71b3d2583 | |||
| 755148242c | |||
| 15fea6f02f | |||
| cb2842e8ef | |||
| 106b67d892 | |||
| 996587f792 | |||
| 3a4bf35c77 | |||
| 46b37c0c26 | |||
| 5b5ba88a97 | |||
| ceb3095a0a | |||
| a31f0cf788 | |||
| 1dc74b171c | |||
| 9266487fe5 | |||
| 5d7ce139bc | |||
| ddb6c87ebf | |||
| 8a6f8f73cd | |||
| f4fc15538d | |||
| 938399d881 | |||
| 53ca421a85 | |||
| 32778d1c03 | |||
| a870bf6e04 | |||
| 7a8f708157 | |||
| e729c5dc51 | |||
| 4bf34c49b7 | |||
| 05511d4900 | |||
| 15da17b595 | |||
| b3c43e428f | |||
| 7bec539722 | |||
| a8dfebb086 | |||
| e72b4fc144 | |||
| 03189e7b20 | |||
| 10bb413187 | |||
| 1c814310b9 | |||
| 3fe9e3162f | |||
| 6da6f7886a | |||
| 8c2029870f | |||
| a5031a44a0 | |||
| 44b27fe472 | |||
| 93fb3b345b | |||
| 8b596697b7 | |||
| c536bbb7e9 | |||
| a978aac129 | |||
| eb8cab4110 | |||
| 0a36d9a5e9 | |||
| 309571039d | |||
| d56f2fb1bb | |||
| 9df8e5e239 | |||
| 444a86cc9f | |||
| 382e10e103 | |||
| dc002d77fa | |||
| aaa55c0dcc | |||
| 0ad3781ae5 | |||
| d738161a2e | |||
| 52c2e9cd24 | |||
| 5fd1a32ae4 | |||
| 655a853857 | |||
| 847da23300 | |||
| d5c07374ff | |||
| 3785752364 | |||
| 398d8a1e41 | |||
| dfd8ca6f79 | |||
| 7cff4367d7 | |||
| 9eaed5e745 | |||
| 68350bb4df | |||
| 12be972fcd | |||
| d200437813 | |||
| 3ffe7a5e68 | |||
| 08ff9f5ece | |||
| 4bee579e48 | |||
| a906c478c9 | |||
| 306d0648c6 | |||
| e99ccf9013 | |||
| 59935a12b9 | |||
| a9ec828506 | |||
| eebeece856 | |||
| 746ab00794 | |||
| b877593d55 | |||
| dcd596907a | |||
| 44c2afbfba | |||
| bd1679b7e0 | |||
| ff7d7e0edd | |||
| 2f088938c6 | |||
| 53adb0e9a6 | |||
| 6789c11a7b | |||
| c56065fcfe | |||
| 52a2ae36f6 | |||
| abcb51d2a6 | |||
| 8b9a40b3d7 | |||
| 4b36df504c | |||
| 35fe5bd5f0 | |||
| 79ae85c728 | |||
| 49ac61304e | |||
| d79cdb0d41 | |||
| a3726b07bf | |||
| 80c35a2053 | |||
| 601b58b670 | |||
| 9cf049775f | |||
| 92122e60c6 | |||
| f20d0f0659 | |||
| 9346a63753 | |||
| bab70ab51e | |||
| 2fc0bb1a03 | |||
| dd0ff8fe60 | |||
| 45a179580d | |||
| 0bcee7f9cc | |||
| af10b117b4 | |||
| 7aa7825209 | |||
| 8b7617294e | |||
| 9985b9b08e | |||
| 840d65881e | |||
| c18f6818e8 | |||
| dcd91cad1b | |||
| 898bb679d2 | |||
| 348d430cd3 | |||
| e3d98633fb | |||
| b451920408 | |||
| 1fa15e76e3 | |||
| 8a58270e03 | |||
| e08d873cc3 | |||
| 3748a1da88 | |||
| 9597d719ac | |||
| 041ce2134f | |||
| 4c17fc51e7 | |||
| 6cf7439053 | |||
| 814ab4937d | |||
| 3d088411e2 | |||
| 39e0c861e2 | |||
| d60efcd7c7 | |||
| 4dff537d21 | |||
| 761c2ade4a | |||
| 115a87af32 | |||
| c7ecaeb145 | |||
| 8e2d63db75 | |||
| 3208bac987 | |||
| fc1f2132fe | |||
| 09125e1e31 | |||
| 6b1164ad8d | |||
| c68a5c776b | |||
| f3ac787f5a | |||
| 751be2c469 | |||
| cfb7f1b016 | |||
| 33e07628db | |||
| b1b7d1d90c | |||
| 6cb9548f72 | |||
| 8207ae1e8d | |||
| 48e0e427eb | |||
| 03e95d2dd3 | |||
| 58b14cc650 | |||
| 3fa8e09545 | |||
| 9d76b354f0 | |||
| 1856a50c56 | |||
| ba0f3ece04 | |||
| e8ad75d924 | |||
| b858284b1e | |||
| 3c6e06f8d5 | |||
| 0d33a00e72 | |||
| 830d0992a7 | |||
| e4192ea172 | |||
| 4bbb1a0fcd | |||
| 77b5937fbb | |||
| 3144d36056 | |||
| 521bbb4b4c | |||
| 0a55e10b94 | |||
| d6e5fd9301 | |||
| 2c625f56fd | |||
| 7db0fa915e | |||
| e424765a61 | |||
| 1ca4dbf1a2 | |||
| 065893e31d | |||
| ef70aa88e1 | |||
| 6a11a199ca | |||
| 7b5ceac9f7 | |||
| 9fe0019ec1 | |||
| 84654a0302 | |||
| cd43444d1f | |||
| b7028896c7 | |||
| c1e86acb3c | |||
| 7d31bd2be9 | |||
| 2e573a66a4 | |||
| 8d128cf2e9 | |||
| d5ffa08480 | |||
| 43ce69bef4 | |||
| 44a0f53e58 | |||
| d74b988f75 | |||
| 10e910186e | |||
| 017f420d42 | |||
| bc186fe6ad | |||
| 09b40cdec7 | |||
| d2721f2d7d | |||
| 3095e19e13 | |||
| c547d033c3 | |||
| 4d4764eefb | |||
| d44a83faaa | |||
| 2337fe37be | |||
| c5b71cb484 | |||
| 480c95d4d6 | |||
| 8a45e965eb | |||
| c9717693d0 | |||
| 0fa3fe3d43 | |||
| ed090f7ecb | |||
| f916f96761 | |||
| 79e766c4e1 | |||
| 183273dcf5 | |||
| b3bb7c694b | |||
| 456a74db05 | |||
| fde724a658 | |||
| 574ee5d0bb | |||
| 03a838ba2d | |||
| b00aacbc41 | |||
| 532c33a13b | |||
| 6a7396bc1d | |||
| 870b2bf36d | |||
| 07b69be7bf | |||
| b41640837c | |||
| bee6ebe3b4 | |||
| 5989298d3f | |||
| 0235f19543 | |||
| cd6c2f68ca | |||
| 75252cf9d5 | |||
| 1da714852f | |||
| fe6b788f6b | |||
| fb3fec299f | |||
| d2f435b265 | |||
| 3a163acd6d | |||
| df4bf9b688 | |||
| 0f67a2f194 | |||
| b75946af46 | |||
| b9b188693c | |||
| 9fe008d407 | |||
| e905c355f9 | |||
| 12821fb70d | |||
| 2aaa316280 | |||
| 918d9b8b1f | |||
| 536a6f3f83 | |||
| b9fd3d37f2 | |||
| e0b87c3ff2 | |||
| 48a9fd46a6 | |||
| 8669c01eaa | |||
| 37949ba1ec | |||
| b2bbaf050c | |||
| 4d6837ea53 | |||
| c8ac970d11 | |||
| a3a429bc41 | |||
| 0726b1b4d9 | |||
| defc4f45ff | |||
| a984127e28 | |||
| 85dd303c88 | |||
| 2958144fd0 | |||
| ade8f18a95 | |||
| 46e2e29ead | |||
| 63a395125a | |||
| 6f973afa36 | |||
| 1ad0ef3f61 | |||
| 90bf46e8d3 | |||
| c1c285db86 | |||
| f1b541a1eb | |||
| 585257c995 | |||
| 93f80cdc50 | |||
| a832193010 | |||
| 383659fb8a | |||
| 4725e55090 | |||
| 07c85ee6f5 | |||
| c02dd9e040 | |||
| 7dae94976d | |||
| 38c360cb74 | |||
| edba640641 | |||
| cf25ae4448 | |||
| e86182af56 | |||
| 902a1ce46e | |||
| 55849cad65 | |||
| edb76af820 | |||
| 04d0b819f6 | |||
| 85aa5fbd46 | |||
| 1054e54c89 | |||
| 2fe5b92035 | |||
| 3a5a161c03 | |||
| 8451321a79 | |||
| 90050a0008 | |||
| 87c9efce4a | |||
| 240e049e46 | |||
| 0899b49bb3 | |||
| fc0660ca8d | |||
| b2ed5dbbc8 | |||
| 94c864503e | |||
| c387c6aebf | |||
| 55c9d39c13 | |||
| 6a3b143876 | |||
| 5f29abfa0d | |||
| ea3364ac12 | |||
| 2b31eac1af | |||
| 093139bc91 | |||
| 6c8a4a6a28 | |||
| f5d2fd6190 | |||
| 4d77bd8f25 | |||
| 1068adbee3 | |||
| 86fc518585 | |||
| 78377a5c67 | |||
| 56f2dcc073 | |||
| 48739b4141 | |||
| 704c344213 | |||
| ee263ba68e | |||
| 692b71af23 | |||
| 7e029f1db0 | |||
| d4875afe31 | |||
| de0e431d00 | |||
| 6b304391b5 | |||
| 8f85e4c88f | |||
| a3b9c3b884 | |||
| 021e16959f | |||
| 37adab76e1 | |||
| 5d342ee1ab | |||
| 697d131397 | |||
| 89d9e30025 | |||
| dfde86df76 | |||
| d55fb25a40 | |||
| 492fcce053 | |||
| d61f17dbd3 | |||
| 8996979dc2 | |||
| 31ddb242a6 | |||
| b7b5d96b3b | |||
| 2e55e5fd05 | |||
| 40ebf8006e | |||
| 8e8643122e | |||
| c00a0b12e4 | |||
| 0e21520fd1 | |||
| 0db233e5c7 | |||
| b9948085a4 | |||
| d62526fde6 | |||
| 134f9c014e | |||
| 056cf472d9 | |||
| 0c161cc0c2 | |||
| 7894894610 | |||
| 7d48bef70a | |||
| 6bc3e257f8 | |||
| adc613d801 | |||
| fd573f6c1c | |||
| 1235e966d2 | |||
| e6a2f049ea | |||
| d9e9dd04af | |||
| 3c2fa15dc2 | |||
| e8c9c20570 | |||
| 0db2578ca0 | |||
| e71d6157f1 | |||
| 7ca2a0df18 | |||
| 7a4af834c2 | |||
| 314a0d124d | |||
| 6f1e86e8a7 | |||
| 840c7ea560 | |||
| 1839730663 | |||
| afd75cc4cf | |||
| 453a1aaa65 | |||
| 4905185e61 | |||
| 0990430513 | |||
| 7df13a044f | |||
| 6135a1f60a | |||
| 5e94adc73a | |||
| 44fc0228a9 | |||
| 4d2bfe3ef3 | |||
| 7f37ff032f | |||
| 30d508464f | |||
| 75b0fed00d | |||
| 927cc1fcf5 | |||
| b3090e277a | |||
| 0b45151a3d | |||
| eb5bfa9b5d | |||
| 4267e94dcd | |||
| fac9e7612f | |||
| 01e8e535f4 | |||
| e5e2e7571f | |||
| c889a8c8de | |||
| cbf2ea7b48 | |||
| 8078347880 | |||
| dd8a802bec | |||
| 6f6602ddd1 | |||
| 33e69db436 | |||
| 13cbec28d6 | |||
| 826083adf6 | |||
| a62bd28e1f | |||
| da1188c6ee | |||
| e22cefbfe3 | |||
| 02ea696070 | |||
| d25702d99d | |||
| c554413327 | |||
| f8c5f238ea | |||
| 66b46c8618 | |||
| 04689d37b7 | |||
| e041cacf4b | |||
| f54fc92b14 | |||
| 6e7b6bcc35 | |||
| e70d5be535 | |||
| 2ce51c8c2e | |||
| c621ec0c50 | |||
| 2ed2a1431d | |||
| 3a425394ca | |||
| d98ba463ad | |||
| c3d97acb31 | |||
| 8dbd464fa4 | |||
| 63a5bb1bbf | |||
| 8c03878808 | |||
| d29e50e50b | |||
| 687bf8b208 | |||
| 10d0807395 | |||
| 0a89c87190 | |||
| a7df92a64c | |||
| 4d3043c041 | |||
| 8261a18472 | |||
| a9f04c0bf4 | |||
| 84334a6bc9 | |||
| 1e5596f977 | |||
| b67fa3a38c | |||
| 8b0123042f | |||
| 16890a659e | |||
| d40ac7f89d | |||
| 2694f48b87 | |||
| 0f91aeb2d3 | |||
| bc0111f895 | |||
| f8a3e9b332 | |||
| db9397fd3e | |||
| 20717ff128 | |||
| a532a88585 | |||
| 0b077bd523 | |||
| e008efcf97 | |||
| 7cf676f9bd | |||
| b83538d08d | |||
| 3c31c340d8 | |||
| 59620456ee | |||
| e42bd29382 | |||
| 18dbc21f96 | |||
| 68d871c47c | |||
| a45a219e5f | |||
| 49788240aa | |||
| 52166500fd | |||
| 3c8fef2655 | |||
| 21af2f2f62 | |||
| 6d01296e8b | |||
| 749efcde21 | |||
| 3af2420da9 | |||
| d0d24f4554 | |||
| 96b44c1771 | |||
| 4a1d07b364 | |||
| 7fbe5349a2 | |||
| d8bad72fd6 | |||
| 927b32c99f | |||
| 27b691bef6 | |||
| d2b9de740d | |||
| eb45b4b1bd | |||
| c545b5e65f | |||
| 992b6486a1 | |||
| 421b1f889b | |||
| 1ee1368e18 | |||
| 1a6a8789c1 | |||
| b5c155db1b | |||
| 6c5b46eadd | |||
| 5abbab2635 | |||
| e5766094f4 | |||
| 96d77988d3 | |||
| e9e4fd4e8b | |||
| b5d9fbf1f4 | |||
| 81dc89f85b | |||
| 76305045db | |||
| c4ba48d7a9 | |||
| d5dcdf13bf | |||
| 7feb3dccbf | |||
| 2251d74fce | |||
| 2c89d3fa7f | |||
| 49cc634678 | |||
| c93e81c632 | |||
| 050e202291 | |||
| efe04d6602 | |||
| 823b8324c7 | |||
| ae2f88a9ee | |||
| 32287c60c4 | |||
| 69c6fa0a24 | |||
| c3e63df7b5 | |||
| fb4f1e30d5 | |||
| 6d0fde218b | |||
| 6d02b0a26d | |||
| 495bf9c954 | |||
| cb1bc95e6b | |||
| 6fdf0266cf | |||
| ee26198e2c | |||
| fe424197da | |||
| 053ca232aa | |||
| 2e8d566bd4 | |||
| dcb0e9b3e8 | |||
| 3497882ea6 | |||
| dc2463a0ab | |||
| b514717076 | |||
| 23932952d8 | |||
| 266ee7626c | |||
| c1ba1f409c | |||
| 0f669e8ca3 | |||
| 9de711a4f5 | |||
| e1965f121c | |||
| b2c9b3500c | |||
| f60d26c4bf | |||
| b43044fee9 | |||
| 2bc66e4574 | |||
| a641a093ad | |||
| 704369d431 | |||
| 5aa8191cd3 | |||
| bb43804d58 | |||
| 7a8d14c85c | |||
| d1e85633fb | |||
| a264f7ab36 | |||
| 4e134772d7 | |||
| 3eb6c91c9e | |||
| e7fd53b809 | |||
| 164ec1e31d | |||
| 373a5f18d6 | |||
| 36058ead7d | |||
| 33fd5b4634 | |||
| 91647978e6 | |||
| d0b64f4bd5 | |||
| c23d58bc27 | |||
| 49a9475c4a | |||
| 364032fe84 | |||
| 5658584803 | |||
| d580200989 | |||
| c2cdde73d1 | |||
| bc74d4d7f8 | |||
| 535c611f2d | |||
| 6efbf65405 | |||
| abb121224d | |||
| 8ad4657d6f | |||
| 6d3f1e00a5 | |||
| c38f6c471d | |||
| 85462de254 | |||
| 61d4f17f5d | |||
| 09cf73cd3c | |||
| ffa3cefa67 | |||
| 8ec2cc8582 | |||
| 75dfa0643b | |||
| d5cde43158 | |||
| 6493a9a1f2 | |||
| f35e059194 | |||
| 3cb3e9d8b7 | |||
| 5dfc5659ad | |||
| c4102992ae | |||
| 219f4e8016 | |||
| 6558140a7c | |||
| db7b61ab80 | |||
| bce5d852e1 | |||
| b960b4a6cd | |||
| 1b10e75168 | |||
| 07359e392c | |||
| e5a5bad240 | |||
| ca92ac60b7 | |||
| e29c21f8df | |||
| 6ceeac93bf | |||
| 9f861096ac | |||
| 520df16885 | |||
| c905ef6083 | |||
| 9652fe99df | |||
| 62d8beff96 | |||
| 7d438e9dbc | |||
| b364221c93 | |||
| b8052bd8fb | |||
| e437ac389e | |||
| 2db2a0921d | |||
| d7889c4e88 | |||
| 559add3f71 | |||
| d615304e83 | |||
| 8e0835e570 | |||
| bb20c89cd5 | |||
| 996cbb5f2b | |||
| 8028d708cb | |||
| 7689905c78 | |||
| 284e42647c | |||
| 3a16c08821 | |||
| 76fb6a318e | |||
| c633f96374 | |||
| 3357be9557 | |||
| 6696e838da | |||
| d0d56f3a1b | |||
| fbf32a6c29 | |||
| 3e37289b68 | |||
| 2d6a45f824 | |||
| ed802336e6 | |||
| b1d1e3b436 | |||
| d33e322b67 | |||
| 60a6e29350 | |||
| 3b0e0c2f16 | |||
| ffb8bccbc5 | |||
| 08170f7e55 | |||
| 28d47b3470 | |||
| 8e3586328c | |||
| f315654d40 | |||
| fdfab18d45 | |||
| d43e77867c | |||
| df03bdd7f9 | |||
| ae5bf6fbb2 | |||
| 04ff63f1b5 | |||
| 71cf39660f | |||
| 7364f2540e | |||
| 5d66f6b2e5 | |||
| fec5fa8caf | |||
| 6f48e138a4 | |||
| 21c15fe4ea | |||
| 67ea8b2936 | |||
| fbaa4d9ab1 | |||
| 19b57c41b7 | |||
| 26d762bc5c | |||
| eab0248aaa | |||
| a5bbe171cb | |||
| 5463086e75 | |||
| 1f7bdd4f5f | |||
| 3cf6aad083 | |||
| b997a2581b | |||
| d7ea078cdf | |||
| d50e0f5fb9 | |||
| c696e9f275 | |||
| 707c8c19a8 | |||
| b37b48b8dc | |||
| 2c7203ab8d | |||
| d1a812042c | |||
| 78d976162d | |||
| 7078162c69 | |||
| 2742b09b5a | |||
| 7b28d51cdd | |||
| d132087b3c | |||
| 124f186983 | |||
| 500a705531 | |||
| 5729947d6c | |||
| 363d0b9126 | |||
| e985452f3b | |||
| 5c9643a23b | |||
| 0e3e3ae37b | |||
| b41d473f64 | |||
| 3de549d2c7 | |||
| 408e9004b0 | |||
| e1a66d5766 | |||
| 99cf2152c4 | |||
| 53e8d425e9 | |||
| 9e5f45da15 | |||
| 4c0caa10e9 | |||
| b9991e4229 | |||
| 4acee49d4b | |||
| d3a8291a7a | |||
| bcae77b989 | |||
| de75ff4bea | |||
| b10a6171a5 | |||
| 6534f45a3a | |||
| 373145d30e | |||
| 5feea74f28 | |||
| e8891d775b | |||
| 0f128f99a1 | |||
| 61e3441bfb | |||
| 15e10d8fde | |||
| ea72e8b656 | |||
| 2ca27c2783 | |||
| f190db0754 | |||
| 8d27570eea | |||
| 24605c9c01 | |||
| 7b59353910 | |||
| ca7f4e5be9 | |||
| ab95ac83c9 | |||
| 51fb57433b | |||
| 0496b94aa8 | |||
| d8d6e99df1 | |||
| 377ba1c9d1 | |||
| 8ebeeb6d90 | |||
| 3c3de065c6 | |||
| a17ec6b1f7 | |||
| cbf8ac6538 | |||
| d4b1295e94 | |||
| db53e17e58 | |||
| f0a3e63743 | |||
| d636e13717 | |||
| dea60e13c0 | |||
| 40a30d997d | |||
| dba8d0305e | |||
| 27072972ab | |||
| d56e95c576 | |||
| d0ef2c43d9 | |||
| 0c0c1acc3b | |||
| 48f796c463 | |||
| 8aab35a70d | |||
| e5f6c24174 | |||
| 4cad06b7ce | |||
| c4e32328fc | |||
| a2ccbf003c | |||
| 10ca2c8681 | |||
| fd63e496e5 | |||
| 567503abc7 | |||
| 4671f59c67 | |||
| 4986d92f3d | |||
| 0b1c7cc35f | |||
| 9e278c88e6 | |||
| c3246060f5 | |||
| b7e025e381 | |||
| 858ec4c0ab | |||
| 65292aaa79 | |||
| 3d57eb633e | |||
| 1e45898d70 | |||
| 61431fb579 | |||
| 00d48e867b | |||
| d5df0c10ab | |||
| af814951f3 | |||
| a54a03aa2f | |||
| 4edfdd8f3b | |||
| 32d669a661 | |||
| 918076a670 | |||
| 752e61d1c7 | |||
| 125f725a03 | |||
| 3c84b48f08 | |||
| 8c32c32c2b | |||
| e664a05d4b | |||
| ba4672b422 | |||
| 09833a1ba5 | |||
| ded0191aae | |||
| 9245e9536a | |||
| 292df12175 | |||
| f988306c17 | |||
| 64ce6cd5a2 |
+4
-1
@@ -6,10 +6,13 @@
|
||||
/captures/
|
||||
/local.properties
|
||||
.DS_Store
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
Thumbs.db
|
||||
build/
|
||||
*.apk
|
||||
*.class
|
||||
*.dex
|
||||
*.iml
|
||||
*.jks
|
||||
gradlew.bat
|
||||
maint/
|
||||
|
||||
+6
-9
@@ -1,9 +1,6 @@
|
||||
[submodule "app/tools/libmnl"]
|
||||
path = app/tools/libmnl
|
||||
url = https://git.netfilter.org/libmnl/
|
||||
[submodule "app/tools/wireguard"]
|
||||
path = app/tools/wireguard
|
||||
url = https://git.zx2c4.com/WireGuard
|
||||
[submodule "app/tools/wireguard-go"]
|
||||
path = app/tools/wireguard-go
|
||||
url = https://git.zx2c4.com/wireguard-go
|
||||
[submodule "tunnel/tools/wireguard-tools"]
|
||||
path = tunnel/tools/wireguard-tools
|
||||
url = https://git.zx2c4.com/wireguard-tools
|
||||
[submodule "tunnel/tools/elf-cleaner"]
|
||||
path = tunnel/tools/elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner
|
||||
|
||||
Generated
+30
-96
@@ -29,16 +29,10 @@
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
<option name="RIGHT_MARGIN" value="100" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
||||
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
|
||||
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
@@ -62,38 +56,19 @@
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<Objective-C-extensions>
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
<XML>
|
||||
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
</XML>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="10" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="10" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="METHOD_ANNOTATION_WRAP" value="0" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="0" />
|
||||
<arrangement>
|
||||
<groups />
|
||||
<rules>
|
||||
@@ -380,7 +355,6 @@
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
@@ -391,6 +365,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -401,6 +376,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -412,6 +388,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -422,6 +399,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -432,6 +410,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -442,6 +421,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -452,6 +432,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -462,64 +443,12 @@
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_width</NAME>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_height</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:width</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:height</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
@@ -527,6 +456,7 @@
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
@@ -536,5 +466,9 @@
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright © &#36;today.year Firstname Lastname <email@example.org> SPDX-License-Identifier:" />
|
||||
<option name="notice" value="Copyright © 2017-&#36;today.year WireGuard LLC. All Rights Reserved. SPDX-License-Identifier: Apache-2.0" />
|
||||
<option name="myName" value="Default" />
|
||||
</copyright>
|
||||
</component>
|
||||
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="myName" value="GPL-2.0-or-later" />
|
||||
<option name="notice" value="Copyright © &#36;today.year Firstname Lastname <email@example.com> SPDX-License-Identifier: GPL-2.0-or-later" />
|
||||
</copyright>
|
||||
</component>
|
||||
Generated
+2
-2
@@ -1,3 +1,3 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings default="GPL-2.0-or-later" />
|
||||
</component>
|
||||
<settings default="Default" />
|
||||
</component>
|
||||
|
||||
Generated
+14
-15
@@ -9,7 +9,6 @@
|
||||
</option>
|
||||
<option name="nonThreadSafeTypes" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AndroidLintGoogleAppIndexingWarning" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="AndroidLintIconExpectedSize" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintTypographyQuotes" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
@@ -40,7 +39,9 @@
|
||||
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToSuperclassField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.util.HashMap,put,java.util.Map,put" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="BadExceptionCaught" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="exceptionsString" value="" />
|
||||
<option name="exceptions">
|
||||
@@ -59,6 +60,9 @@
|
||||
<inspection_tool class="CallToStringConcatCanBeReplacedByOperator" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CannotResolve" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CatchMayIgnoreException" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreCatchBlocksWithComments" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassInitializer" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
@@ -103,9 +107,6 @@
|
||||
<inspection_tool class="DoubleLiteralMayBeFloatLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateAlternationBranch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateCondition" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSideEffectConditions" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DuplicateDeclarations" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="DynamicRegexReplaceableByCompiledPattern" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ElementOnlyUsedFromTestCode" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
@@ -150,7 +151,6 @@
|
||||
<inspection_tool class="FieldMayBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldMustBeInitialized" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldNotUsedInToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FinalMethodInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ForLoopReplaceableByWhile" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
@@ -197,7 +197,6 @@
|
||||
<inspection_tool class="ImplicitSubclassInspection" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="IncompatibleTypes" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="InitializerIssues" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
@@ -216,7 +215,6 @@
|
||||
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstantiationOfUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IntLiteralMayBeLongLiteral" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IntegerDivisionInFloatingPointContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IntegerTypeRequired" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
<inspection_tool class="InterfaceNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
@@ -295,6 +293,7 @@
|
||||
<option name="ignoreForLoopDeclarations" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MultipleVariablesInDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NamedResource" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="NativeMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
@@ -348,7 +347,6 @@
|
||||
<inspection_tool class="OverriddenMethodCallDuringObjectConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageInfoWithoutPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PointerTypeRequired" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="PointlessNullCheck" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProtectedField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProtectedInnerClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
@@ -386,7 +384,6 @@
|
||||
<option name="ignorePrivateMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReturnOfDateField" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ScalarTypeRequired" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableClassInSecureContext" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
@@ -396,7 +393,13 @@
|
||||
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticCallOnSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticFieldReferenceOnSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticImport" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="allowedClasses">
|
||||
<set>
|
||||
<option value="org.junit.Assert" />
|
||||
</set>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StaticInheritance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="m_regex" value="[a-z][A-Za-z\d]*" />
|
||||
@@ -434,7 +437,6 @@
|
||||
<inspection_tool class="TemplateArgumentsIssues" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="TestMethodWithoutAssertion" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="assertionMethods" value="org.junit.Assert,assert.*|fail.*,junit.framework.Assert,assert.*|fail.*,org.junit.jupiter.api.Assertions,assert.*|fail.*,org.mockito.Mockito,verify.*,org.mockito.InOrder,verify,org.junit.rules.ExpectedException,expect.*,org.hamcrest.MatcherAssert,assertThat" />
|
||||
<option name="assertKeywordIsAssertion" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TestNGMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ThisEscapedInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
@@ -442,7 +444,6 @@
|
||||
<option name="ignoreRethrownExceptions" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ThrowsRuntimeException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ToArrayCallWithZeroLengthArrayArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TooBroadScope" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_allowConstructorAsInitializer" value="false" />
|
||||
<option name="m_onlyLookAtBlocks" value="true" />
|
||||
@@ -468,10 +469,8 @@
|
||||
<inspection_tool class="UnnecessaryBlockStatement" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSwitchBranches" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryCallToStringValueOf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryDefault" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryExplicitNumericCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryFullyQualifiedName" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreJavadoc" value="false" />
|
||||
|
||||
@@ -1,338 +1,202 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Preamble
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
1. Definitions.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
"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.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
"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.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
"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).
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
"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.
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
"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."
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
"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.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
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.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
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.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
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 cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
(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
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
(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.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
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.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of 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.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
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.
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
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.
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
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.
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
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.
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
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.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
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
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
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.
|
||||
|
||||
@@ -7,25 +7,34 @@ This is an Android GUI for [WireGuard](https://www.wireguard.com/). It [opportun
|
||||
## Building
|
||||
|
||||
```
|
||||
$ git clone https://git.zx2c4.com/wireguard-android
|
||||
$ git clone --recurse-submodules https://git.zx2c4.com/wireguard-android
|
||||
$ cd wireguard-android
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
$ ./gradlew assembleRelease
|
||||
```
|
||||
|
||||
## License
|
||||
macOS users may need [flock(1)](https://github.com/discoteq/flock).
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
## Embedding
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
The tunnel library is [on Maven Central](https://search.maven.org/artifact/com.wireguard.android/tunnel), alongside [extensive class library documentation](https://javadoc.io/doc/com.wireguard.android/tunnel).
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
```
|
||||
implementation 'com.wireguard.android:tunnel:$wireguardTunnelVersion'
|
||||
```
|
||||
|
||||
The library makes use of Java 8 features, so be sure to support those in your gradle configuration with [desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring):
|
||||
|
||||
```
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
coreLibraryDesugaringEnabled = true
|
||||
}
|
||||
dependencies {
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
|
||||
}
|
||||
```
|
||||
|
||||
## Translating
|
||||
|
||||
Please help us translate the app into several languages on [our translation platform](https://crowdin.com/project/WireGuard).
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
buildToolsVersion '27.0.3'
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
compileSdkVersion 27
|
||||
dataBinding {
|
||||
enabled true
|
||||
}
|
||||
defaultConfig {
|
||||
applicationId 'com.wireguard.android'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 417
|
||||
versionName '0.0.20180526'
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path 'tools/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
ext {
|
||||
databindingVersion = '3.1.2'
|
||||
supportLibsVersion = '27.1.1'
|
||||
daggerVersion = '2.14.1'
|
||||
fabLibVersion = '1.10.1'
|
||||
streamsupportVersion = '1.6.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
implementation "com.android.databinding:library:$databindingVersion"
|
||||
implementation "com.android.support:appcompat-v7:$supportLibsVersion"
|
||||
implementation "com.android.support:cardview-v7:$supportLibsVersion"
|
||||
implementation "com.android.support:design:$supportLibsVersion"
|
||||
implementation "com.android.support:preference-v14:$supportLibsVersion"
|
||||
implementation "com.android.support:support-annotations:$supportLibsVersion"
|
||||
implementation "com.getbase:floatingactionbutton:$fabLibVersion"
|
||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||
implementation "net.sourceforge.streamsupport:android-retrofuture:$streamsupportVersion"
|
||||
implementation "net.sourceforge.streamsupport:android-retrostreams:$streamsupportVersion"
|
||||
}
|
||||
Vendored
-26
@@ -1,26 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /opt/android-sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-dontwarn sun.misc.Unsafe
|
||||
@@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.wireguard.android"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:allowBackup="false"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<activity android:name=".activity.MainActivity">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:parentActivityName=".activity.MainActivity"
|
||||
android:theme="@style/SettingsTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".activity.TunnelCreatorActivity"
|
||||
android:label="@string/create_activity_title"
|
||||
android:parentActivityName=".activity.MainActivity" />
|
||||
|
||||
<receiver android:name=".BootShutdownReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".backend.WgQuickBackend$WgQuickChangeReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="com.wireguard.android.WGQUICK_CHANGE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".backend.GoBackend$VpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
|
||||
android:value="false" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".QuickTileService"
|
||||
android:icon="@drawable/ic_tile"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.ACTIVE_TILE"
|
||||
android:value="false" />
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.wireguard.android.backend.Backend;
|
||||
import com.wireguard.android.backend.GoBackend;
|
||||
import com.wireguard.android.backend.WgQuickBackend;
|
||||
import com.wireguard.android.configStore.ConfigStore;
|
||||
import com.wireguard.android.configStore.FileConfigStore;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.AsyncWorker;
|
||||
import com.wireguard.android.util.RootShell;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import javax.inject.Scope;
|
||||
|
||||
import dagger.Component;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/**
|
||||
* Base context for the WireGuard Android application. This class (instantiated once during the
|
||||
* application lifecycle) maintains and mediates access to the global state of the application.
|
||||
*/
|
||||
|
||||
public class Application extends android.app.Application {
|
||||
private static ApplicationComponent component;
|
||||
|
||||
public static ApplicationComponent getComponent() {
|
||||
if (component == null)
|
||||
throw new IllegalStateException("Application instance not yet created");
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
component = DaggerApplication_ApplicationComponent.builder()
|
||||
.applicationModule(new ApplicationModule(this))
|
||||
.build();
|
||||
component.getTunnelManager().onCreate();
|
||||
}
|
||||
|
||||
@ApplicationScope
|
||||
@Component(modules = ApplicationModule.class)
|
||||
public interface ApplicationComponent {
|
||||
AsyncWorker getAsyncWorker();
|
||||
|
||||
Class getBackendType();
|
||||
|
||||
ToolsInstaller getToolsInstaller();
|
||||
|
||||
TunnelManager getTunnelManager();
|
||||
}
|
||||
|
||||
@Qualifier
|
||||
public @interface ApplicationContext {
|
||||
}
|
||||
|
||||
@Qualifier
|
||||
public @interface ApplicationHandler {
|
||||
}
|
||||
|
||||
@Scope
|
||||
public @interface ApplicationScope {
|
||||
}
|
||||
|
||||
@Module
|
||||
public static final class ApplicationModule {
|
||||
private final Context context;
|
||||
|
||||
private ApplicationModule(final Application application) {
|
||||
context = application.getApplicationContext();
|
||||
}
|
||||
|
||||
@ApplicationScope
|
||||
@Provides
|
||||
public static Backend getBackend(@ApplicationContext final Context context,
|
||||
final RootShell rootShell,
|
||||
final ToolsInstaller toolsInstaller) {
|
||||
if (new File("/sys/module/wireguard").exists())
|
||||
return new WgQuickBackend(context, rootShell, toolsInstaller);
|
||||
else
|
||||
return new GoBackend(context);
|
||||
}
|
||||
|
||||
@ApplicationScope
|
||||
@Provides
|
||||
public static Class getBackendType(final Backend backend) {
|
||||
return backend.getClass();
|
||||
}
|
||||
|
||||
@ApplicationScope
|
||||
@Provides
|
||||
public static ConfigStore getConfigStore(@ApplicationContext final Context context) {
|
||||
return new FileConfigStore(context);
|
||||
}
|
||||
|
||||
|
||||
@ApplicationScope
|
||||
@Provides
|
||||
public static Executor getExecutor() {
|
||||
return AsyncTask.SERIAL_EXECUTOR;
|
||||
}
|
||||
|
||||
@ApplicationHandler
|
||||
@ApplicationScope
|
||||
@Provides
|
||||
public static Handler getHandler() {
|
||||
return new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@ApplicationScope
|
||||
@Provides
|
||||
public static SharedPreferences getPreferences(@ApplicationContext final Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
@ApplicationContext
|
||||
@ApplicationScope
|
||||
@Provides
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
|
||||
public class BootShutdownReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "WireGuard/" + BootShutdownReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
if (action == null)
|
||||
return;
|
||||
final TunnelManager tunnelManager = Application.getComponent().getTunnelManager();
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
|
||||
Log.i(TAG, "Broadcast receiver restoring state (boot)");
|
||||
tunnelManager.restoreState().whenComplete(ExceptionLoggers.D);
|
||||
} else if (Intent.ACTION_SHUTDOWN.equals(action)) {
|
||||
Log.i(TAG, "Broadcast receiver saving state (shutdown)");
|
||||
tunnelManager.saveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.databinding.Observable;
|
||||
import android.databinding.Observable.OnPropertyChangedCallback;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.service.quicksettings.TileService;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.wireguard.android.activity.MainActivity;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Service that maintains the application's custom Quick Settings tile. This service is bound by the
|
||||
* system framework as necessary to update the appearance of the tile in the system UI, and to
|
||||
* forward click events to the application.
|
||||
*/
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public class QuickTileService extends TileService {
|
||||
private static final String TAG = "WireGuard/" + QuickTileService.class.getSimpleName();
|
||||
|
||||
private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
|
||||
private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
|
||||
private Tunnel tunnel;
|
||||
private TunnelManager tunnelManager;
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
if (tunnel != null) {
|
||||
tunnel.setState(State.TOGGLE).whenComplete(this::onToggleFinished);
|
||||
} else {
|
||||
final Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivityAndCollapse(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
tunnelManager = Application.getComponent().getTunnelManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartListening() {
|
||||
tunnelManager.addOnPropertyChangedCallback(onTunnelChangedCallback);
|
||||
if (tunnel != null)
|
||||
tunnel.addOnPropertyChangedCallback(onStateChangedCallback);
|
||||
updateTile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopListening() {
|
||||
if (tunnel != null)
|
||||
tunnel.removeOnPropertyChangedCallback(onStateChangedCallback);
|
||||
tunnelManager.removeOnPropertyChangedCallback(onTunnelChangedCallback);
|
||||
}
|
||||
|
||||
private void onToggleFinished(@SuppressWarnings("unused") final State state,
|
||||
final Throwable throwable) {
|
||||
if (throwable == null)
|
||||
return;
|
||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
final String message = getString(R.string.toggle_error, error);
|
||||
Log.e(TAG, message, throwable);
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void updateTile() {
|
||||
// Update the tunnel.
|
||||
final Tunnel newTunnel = tunnelManager.getLastUsedTunnel();
|
||||
if (newTunnel != tunnel) {
|
||||
if (tunnel != null)
|
||||
tunnel.removeOnPropertyChangedCallback(onStateChangedCallback);
|
||||
tunnel = newTunnel;
|
||||
if (tunnel != null)
|
||||
tunnel.addOnPropertyChangedCallback(onStateChangedCallback);
|
||||
}
|
||||
// Update the tile contents.
|
||||
final String label;
|
||||
final int state;
|
||||
final Tile tile = getQsTile();
|
||||
if (tunnel != null) {
|
||||
label = tunnel.getName();
|
||||
state = tunnel.getState() == Tunnel.State.UP ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
|
||||
} else {
|
||||
label = getString(R.string.app_name);
|
||||
state = Tile.STATE_INACTIVE;
|
||||
}
|
||||
if (tile == null)
|
||||
return;
|
||||
tile.setLabel(label);
|
||||
if (tile.getState() != state) {
|
||||
// The icon must be changed every time the state changes, or the shade will not change.
|
||||
final Integer iconResource = state == Tile.STATE_ACTIVE ? R.drawable.ic_tile
|
||||
: R.drawable.ic_tile_disabled;
|
||||
tile.setIcon(Icon.createWithResource(this, iconResource));
|
||||
tile.setState(state);
|
||||
}
|
||||
tile.updateTile();
|
||||
}
|
||||
|
||||
private final class OnStateChangedCallback extends OnPropertyChangedCallback {
|
||||
@Override
|
||||
public void onPropertyChanged(final Observable sender, final int propertyId) {
|
||||
if (!Objects.equals(sender, tunnel)) {
|
||||
sender.removeOnPropertyChangedCallback(this);
|
||||
return;
|
||||
}
|
||||
if (propertyId != 0 && propertyId != BR.state)
|
||||
return;
|
||||
updateTile();
|
||||
}
|
||||
}
|
||||
|
||||
private final class OnTunnelChangedCallback extends OnPropertyChangedCallback {
|
||||
@Override
|
||||
public void onPropertyChanged(final Observable sender, final int propertyId) {
|
||||
if (propertyId != 0 && propertyId != BR.lastUsedTunnel)
|
||||
return;
|
||||
updateTile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.databinding.CallbackRegistry;
|
||||
import android.databinding.CallbackRegistry.NotifierCallback;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.backend.GoBackend;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Base class for activities that need to remember the currently-selected tunnel.
|
||||
*/
|
||||
|
||||
public abstract class BaseActivity extends AppCompatActivity {
|
||||
private static final String KEY_SELECTED_TUNNEL = "selected_tunnel";
|
||||
|
||||
private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry();
|
||||
private Tunnel selectedTunnel;
|
||||
|
||||
public void addOnSelectedTunnelChangedListener(
|
||||
final OnSelectedTunnelChangedListener listener) {
|
||||
selectionChangeRegistry.add(listener);
|
||||
}
|
||||
|
||||
public Tunnel getSelectedTunnel() {
|
||||
return selectedTunnel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
// Restore the saved tunnel if there is one; otherwise grab it from the arguments.
|
||||
String savedTunnelName = null;
|
||||
if (savedInstanceState != null)
|
||||
savedTunnelName = savedInstanceState.getString(KEY_SELECTED_TUNNEL);
|
||||
else if (getIntent() != null)
|
||||
savedTunnelName = getIntent().getStringExtra(KEY_SELECTED_TUNNEL);
|
||||
if (savedTunnelName != null) {
|
||||
final TunnelManager tunnelManager = Application.getComponent().getTunnelManager();
|
||||
selectedTunnel = tunnelManager.getTunnels().get(savedTunnelName);
|
||||
}
|
||||
|
||||
// The selected tunnel must be set before the superclass method recreates fragments.
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Application.getComponent().getBackendType() == GoBackend.class) {
|
||||
final Intent intent = GoBackend.VpnService.prepare(this);
|
||||
if (intent != null) {
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(final Bundle outState) {
|
||||
if (selectedTunnel != null)
|
||||
outState.putString(KEY_SELECTED_TUNNEL, selectedTunnel.getName());
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
protected abstract void onSelectedTunnelChanged(Tunnel oldTunnel, Tunnel newTunnel);
|
||||
|
||||
public void removeOnSelectedTunnelChangedListener(
|
||||
final OnSelectedTunnelChangedListener listener) {
|
||||
selectionChangeRegistry.remove(listener);
|
||||
}
|
||||
|
||||
public void setSelectedTunnel(final Tunnel tunnel) {
|
||||
final Tunnel oldTunnel = selectedTunnel;
|
||||
if (Objects.equals(oldTunnel, tunnel))
|
||||
return;
|
||||
selectedTunnel = tunnel;
|
||||
onSelectedTunnelChanged(oldTunnel, tunnel);
|
||||
selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, tunnel);
|
||||
}
|
||||
|
||||
public interface OnSelectedTunnelChangedListener {
|
||||
void onSelectedTunnelChanged(Tunnel oldTunnel, Tunnel newTunnel);
|
||||
}
|
||||
|
||||
private static final class SelectionChangeNotifier
|
||||
extends NotifierCallback<OnSelectedTunnelChangedListener, Tunnel, Tunnel> {
|
||||
@Override
|
||||
public void onNotifyCallback(final OnSelectedTunnelChangedListener listener,
|
||||
final Tunnel oldTunnel, final int ignored,
|
||||
final Tunnel newTunnel) {
|
||||
listener.onSelectedTunnelChanged(oldTunnel, newTunnel);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SelectionChangeRegistry
|
||||
extends CallbackRegistry<OnSelectedTunnelChangedListener, Tunnel, Tunnel> {
|
||||
private SelectionChangeRegistry() {
|
||||
super(new SelectionChangeNotifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.fragment.TunnelDetailFragment;
|
||||
import com.wireguard.android.fragment.TunnelEditorFragment;
|
||||
import com.wireguard.android.fragment.TunnelListFragment;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
|
||||
import java9.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* CRUD interface for WireGuard tunnels. This activity serves as the main entry point to the
|
||||
* WireGuard application, and contains several fragments for listing, viewing details of, and
|
||||
* editing the configuration and interface state of WireGuard tunnels.
|
||||
*/
|
||||
|
||||
public class MainActivity extends BaseActivity {
|
||||
private static final String KEY_STATE = "fragment_state";
|
||||
private static final String TAG = "WireGuard/" + MainActivity.class.getSimpleName();
|
||||
private State state = State.EMPTY;
|
||||
|
||||
private boolean moveToState(final State nextState) {
|
||||
if (state == nextState)
|
||||
return false;
|
||||
final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
Log.i(TAG, "Moving from " + state.name() + " to " + nextState.name());
|
||||
if (nextState.layer > state.layer + 1) {
|
||||
moveToState(State.ofLayer(state.layer + 1));
|
||||
moveToState(nextState);
|
||||
return true;
|
||||
} else if (nextState.layer == state.layer + 1) {
|
||||
final Fragment fragment = Fragment.instantiate(this, nextState.fragment);
|
||||
final FragmentTransaction transaction = fragmentManager.beginTransaction()
|
||||
.replace(R.id.master_fragment, fragment)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
||||
if (state.layer > 0)
|
||||
transaction.addToBackStack(null);
|
||||
transaction.commit();
|
||||
} else if (nextState.layer == state.layer - 1) {
|
||||
if (fragmentManager.getBackStackEntryCount() == 0)
|
||||
return false;
|
||||
fragmentManager.popBackStack();
|
||||
} else if (nextState.layer < state.layer - 1) {
|
||||
moveToState(State.ofLayer(state.layer - 1));
|
||||
moveToState(nextState);
|
||||
return true;
|
||||
}
|
||||
state = nextState;
|
||||
if (state.layer <= State.LIST.layer)
|
||||
setSelectedTunnel(null);
|
||||
updateActionBar();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
TunnelListFragment fragment = null;
|
||||
try {
|
||||
fragment =
|
||||
((TunnelListFragment)
|
||||
getSupportFragmentManager().getFragments().get(0));
|
||||
} catch (final ClassCastException ignored) {
|
||||
}
|
||||
if (fragment == null || !(fragment.collapseActionMenu())) {
|
||||
if (!moveToState(State.ofLayer(state.layer - 1)))
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main_activity);
|
||||
if (savedInstanceState != null && savedInstanceState.getString(KEY_STATE) != null)
|
||||
state = State.valueOf(savedInstanceState.getString(KEY_STATE));
|
||||
if (state == State.EMPTY) {
|
||||
State initialState = getSelectedTunnel() != null ? State.DETAIL : State.LIST;
|
||||
if (getIntent() != null && getIntent().getStringExtra(KEY_STATE) != null)
|
||||
initialState = State.valueOf(getIntent().getStringExtra(KEY_STATE));
|
||||
moveToState(initialState);
|
||||
}
|
||||
updateActionBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.main_activity, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// The back arrow in the action bar should act the same as the back button.
|
||||
moveToState(State.ofLayer(state.layer - 1));
|
||||
return true;
|
||||
case R.id.menu_action_edit:
|
||||
if (getSelectedTunnel() != null)
|
||||
moveToState(State.EDITOR);
|
||||
return true;
|
||||
case R.id.menu_action_save:
|
||||
// This menu item is handled by the editor fragment.
|
||||
return false;
|
||||
case R.id.menu_settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(final Bundle outState) {
|
||||
outState.putString(KEY_STATE, state.name());
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
moveToState(newTunnel != null ? State.DETAIL : State.LIST);
|
||||
}
|
||||
|
||||
private void updateActionBar() {
|
||||
if (getSupportActionBar() != null)
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(state.layer > State.LIST.layer);
|
||||
}
|
||||
|
||||
private enum State {
|
||||
EMPTY(null, 0),
|
||||
LIST(TunnelListFragment.class, 1),
|
||||
DETAIL(TunnelDetailFragment.class, 2),
|
||||
EDITOR(TunnelEditorFragment.class, 3);
|
||||
|
||||
private final String fragment;
|
||||
private final int layer;
|
||||
|
||||
State(final Class<? extends Fragment> fragment, final int layer) {
|
||||
this.fragment = fragment != null ? fragment.getName() : null;
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
private static State ofLayer(final int layer) {
|
||||
return Stream.of(State.values()).filter(s -> s.layer == layer).findFirst().get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.backend.WgQuickBackend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface for changing application-global persistent settings.
|
||||
*/
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity {
|
||||
private final Map<Integer, PermissionRequestCallback> permissionRequestCallbacks = new HashMap<>();
|
||||
private int permissionRequestCounter;
|
||||
|
||||
public void ensurePermissions(final String[] permissions, final PermissionRequestCallback cb) {
|
||||
final List<String> needPermissions = new ArrayList<>(permissions.length);
|
||||
for (final String permission : permissions) {
|
||||
if (ContextCompat.checkSelfPermission(this, permission)
|
||||
!= PackageManager.PERMISSION_GRANTED)
|
||||
needPermissions.add(permission);
|
||||
}
|
||||
if (needPermissions.isEmpty()) {
|
||||
final int[] granted = new int[permissions.length];
|
||||
Arrays.fill(granted, PackageManager.PERMISSION_GRANTED);
|
||||
cb.done(permissions, granted);
|
||||
return;
|
||||
}
|
||||
final int idx = permissionRequestCounter++;
|
||||
permissionRequestCallbacks.put(idx, cb);
|
||||
ActivityCompat.requestPermissions(this,
|
||||
needPermissions.toArray(new String[needPermissions.size()]), idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(final int requestCode,
|
||||
@NonNull final String[] permissions,
|
||||
@NonNull final int[] grantResults) {
|
||||
final PermissionRequestCallback f = permissionRequestCallbacks.get(requestCode);
|
||||
if (f != null) {
|
||||
permissionRequestCallbacks.remove(requestCode);
|
||||
f.done(permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
public interface PermissionRequestCallback {
|
||||
void done(String[] permissions, int[] grantResults);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String key) {
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
if (Application.getComponent().getBackendType() != WgQuickBackend.class) {
|
||||
final Preference toolsInstaller =
|
||||
getPreferenceManager().findPreference("tools_installer");
|
||||
getPreferenceScreen().removePreference(toolsInstaller);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.wireguard.android.fragment.TunnelEditorFragment;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
|
||||
/**
|
||||
* Standalone activity for creating tunnels.
|
||||
*/
|
||||
|
||||
public class TunnelCreatorActivity extends BaseActivity {
|
||||
@Override
|
||||
@SuppressWarnings("UnnecessaryFullyQualifiedName")
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(android.R.id.content, new TunnelEditorFragment())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.model.Tunnel.Statistics;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Interface for implementations of the WireGuard secure network tunnel.
|
||||
*/
|
||||
|
||||
public interface Backend {
|
||||
/**
|
||||
* Update the volatile configuration of a running tunnel and return the resulting configuration.
|
||||
* If the tunnel is not up, return the configuration that would result (if known), or else
|
||||
* simply return the given configuration.
|
||||
*
|
||||
* @param tunnel The tunnel to apply the configuration to.
|
||||
* @param config The new configuration for this tunnel.
|
||||
* @return The updated configuration of the tunnel.
|
||||
*/
|
||||
Config applyConfig(Tunnel tunnel, Config config) throws Exception;
|
||||
|
||||
/**
|
||||
* Enumerate the names of currently-running tunnels.
|
||||
*
|
||||
* @return The set of running tunnel names.
|
||||
*/
|
||||
Set<String> enumerate();
|
||||
|
||||
/**
|
||||
* Get the actual state of a tunnel.
|
||||
*
|
||||
* @param tunnel The tunnel to examine the state of.
|
||||
* @return The state of the tunnel.
|
||||
*/
|
||||
State getState(Tunnel tunnel) throws Exception;
|
||||
|
||||
/**
|
||||
* Get statistics about traffic and errors on this tunnel. If the tunnel is not running, the
|
||||
* statistics object will be filled with zero values.
|
||||
*
|
||||
* @param tunnel The tunnel to retrieve statistics for.
|
||||
* @return The statistics for the tunnel.
|
||||
*/
|
||||
Statistics getStatistics(Tunnel tunnel) throws Exception;
|
||||
|
||||
/**
|
||||
* Set the state of a tunnel.
|
||||
*
|
||||
* @param tunnel The tunnel to control the state of.
|
||||
* @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
|
||||
* {@code TOGGLE}.
|
||||
* @return The updated state of the tunnel.
|
||||
*/
|
||||
State setState(Tunnel tunnel, State state) throws Exception;
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.v4.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.model.Tunnel.Statistics;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.config.IPCidr;
|
||||
import com.wireguard.config.Interface;
|
||||
import com.wireguard.config.Peer;
|
||||
import com.wireguard.crypto.KeyEncoding;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.Formatter;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
|
||||
public final class GoBackend implements Backend {
|
||||
private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName();
|
||||
private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>();
|
||||
|
||||
static {
|
||||
System.loadLibrary("wg-go");
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private Tunnel currentTunnel;
|
||||
private int currentTunnelHandle = -1;
|
||||
|
||||
public GoBackend(final Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private static native int wgGetSocketV4(int handle);
|
||||
|
||||
private static native int wgGetSocketV6(int handle);
|
||||
|
||||
private static native void wgTurnOff(int handle);
|
||||
|
||||
private static native int wgTurnOn(String ifName, int tunFd, String settings);
|
||||
|
||||
@Override
|
||||
public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
|
||||
if (tunnel.getState() == State.UP) {
|
||||
// Restart the tunnel to apply the new config.
|
||||
setStateInternal(tunnel, tunnel.getConfig(), State.DOWN);
|
||||
try {
|
||||
setStateInternal(tunnel, config, State.UP);
|
||||
} catch (final Exception e) {
|
||||
// The new configuration didn't work, so try to go back to the old one.
|
||||
setStateInternal(tunnel, tunnel.getConfig(), State.UP);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> enumerate() {
|
||||
if (currentTunnel != null) {
|
||||
final Set<String> runningTunnels = new ArraySet<>();
|
||||
runningTunnels.add(currentTunnel.getName());
|
||||
return runningTunnels;
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState(final Tunnel tunnel) {
|
||||
return currentTunnel == tunnel ? State.UP : State.DOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statistics getStatistics(final Tunnel tunnel) {
|
||||
return new Statistics();
|
||||
}
|
||||
|
||||
@Override
|
||||
public State setState(final Tunnel tunnel, State state) throws Exception {
|
||||
final State originalState = getState(tunnel);
|
||||
if (state == State.TOGGLE)
|
||||
state = originalState == State.UP ? State.DOWN : State.UP;
|
||||
if (state == originalState)
|
||||
return originalState;
|
||||
if (state == State.UP && currentTunnel != null)
|
||||
throw new IllegalStateException("Only one userspace tunnel can run at a time");
|
||||
Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state);
|
||||
setStateInternal(tunnel, tunnel.getConfig(), state);
|
||||
return getState(tunnel);
|
||||
}
|
||||
|
||||
private void setStateInternal(final Tunnel tunnel, final Config config, final State state)
|
||||
throws Exception {
|
||||
|
||||
if (state == State.UP) {
|
||||
Log.i(TAG, "Bringing tunnel up");
|
||||
|
||||
if (VpnService.prepare(context) != null)
|
||||
throw new Exception("VPN service not authorized by user");
|
||||
|
||||
final VpnService service;
|
||||
if (!vpnService.isDone())
|
||||
startVpnService();
|
||||
|
||||
try {
|
||||
service = vpnService.get(2, TimeUnit.SECONDS);
|
||||
} catch (final TimeoutException e) {
|
||||
throw new Exception("Unable to start Android VPN service", e);
|
||||
}
|
||||
|
||||
if (currentTunnelHandle != -1) {
|
||||
Log.w(TAG, "Tunnel already up");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build config
|
||||
final Interface iface = config.getInterface();
|
||||
final String goConfig;
|
||||
try (Formatter fmt = new Formatter(new StringBuilder())) {
|
||||
fmt.format("replace_peers=true\n");
|
||||
if (iface.getPrivateKey() != null)
|
||||
fmt.format("private_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(iface.getPrivateKey())));
|
||||
if (iface.getListenPort() != 0)
|
||||
fmt.format("listen_port=%d\n", config.getInterface().getListenPort());
|
||||
for (final Peer peer : config.getPeers()) {
|
||||
if (peer.getPublicKey() != null)
|
||||
fmt.format("public_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(peer.getPublicKey())));
|
||||
if (peer.getPreSharedKey() != null)
|
||||
fmt.format("preshared_key=%s\n", KeyEncoding.keyToHex(KeyEncoding.keyFromBase64(peer.getPreSharedKey())));
|
||||
if (peer.getEndpoint() != null)
|
||||
fmt.format("endpoint=%s\n", peer.getResolvedEndpointString());
|
||||
if (peer.getPersistentKeepalive() != 0)
|
||||
fmt.format("persistent_keepalive_interval=%d\n", peer.getPersistentKeepalive());
|
||||
for (final IPCidr addr : peer.getAllowedIPs()) {
|
||||
fmt.format("allowed_ip=%s\n", addr.toString());
|
||||
}
|
||||
}
|
||||
goConfig = fmt.toString();
|
||||
}
|
||||
|
||||
// Create the vpn tunnel with android API
|
||||
final VpnService.Builder builder = service.getBuilder();
|
||||
builder.setSession(tunnel.getName());
|
||||
|
||||
for (final IPCidr addr : config.getInterface().getAddresses())
|
||||
builder.addAddress(addr.getAddress(), addr.getCidr());
|
||||
|
||||
for (final InetAddress addr : config.getInterface().getDnses())
|
||||
builder.addDnsServer(addr.getHostAddress());
|
||||
|
||||
for (final Peer peer : config.getPeers()) {
|
||||
for (final IPCidr addr : peer.getAllowedIPs())
|
||||
builder.addRoute(addr.getAddress(), addr.getCidr());
|
||||
}
|
||||
|
||||
int mtu = config.getInterface().getMtu();
|
||||
if (mtu == 0)
|
||||
mtu = 1280;
|
||||
builder.setMtu(mtu);
|
||||
|
||||
builder.setBlocking(true);
|
||||
final ParcelFileDescriptor tun = builder.establish();
|
||||
if (tun == null)
|
||||
throw new Exception("Unable to create tun device");
|
||||
|
||||
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
|
||||
if (currentTunnelHandle < 0)
|
||||
throw new Exception("Unable to turn tunnel on (wgTurnOn return " + currentTunnelHandle + ')');
|
||||
|
||||
currentTunnel = tunnel;
|
||||
|
||||
service.protect(wgGetSocketV4(currentTunnelHandle));
|
||||
service.protect(wgGetSocketV6(currentTunnelHandle));
|
||||
} else {
|
||||
Log.i(TAG, "Bringing tunnel down");
|
||||
|
||||
if (currentTunnelHandle == -1) {
|
||||
Log.w(TAG, "Tunnel already down");
|
||||
return;
|
||||
}
|
||||
|
||||
wgTurnOff(currentTunnelHandle);
|
||||
currentTunnel = null;
|
||||
currentTunnelHandle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void startVpnService() {
|
||||
context.startService(new Intent(context, VpnService.class));
|
||||
}
|
||||
|
||||
public static class VpnService extends android.net.VpnService {
|
||||
public Builder getBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
vpnService.complete(this);
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
for (final Tunnel tunnel : Application.getComponent().getTunnelManager().getTunnels()) {
|
||||
if (tunnel != null && tunnel.getState() != State.DOWN)
|
||||
tunnel.setState(State.DOWN);
|
||||
}
|
||||
vpnService = vpnService.newIncompleteFuture();
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.model.Tunnel.Statistics;
|
||||
import com.wireguard.android.util.RootShell;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* WireGuard backend that uses {@code wg-quick} to implement tunnel configuration.
|
||||
*/
|
||||
|
||||
public final class WgQuickBackend implements Backend {
|
||||
private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName();
|
||||
|
||||
private final File localTemporaryDir;
|
||||
private final RootShell rootShell;
|
||||
private final ToolsInstaller toolsInstaller;
|
||||
|
||||
public WgQuickBackend(final Context context, final RootShell rootShell,
|
||||
final ToolsInstaller toolsInstaller) {
|
||||
localTemporaryDir = new File(context.getCacheDir(), "tmp");
|
||||
this.rootShell = rootShell;
|
||||
this.toolsInstaller = toolsInstaller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
|
||||
if (tunnel.getState() == State.UP) {
|
||||
// Restart the tunnel to apply the new config.
|
||||
setStateInternal(tunnel, tunnel.getConfig(), State.DOWN);
|
||||
try {
|
||||
setStateInternal(tunnel, config, State.UP);
|
||||
} catch (final Exception e) {
|
||||
// The new configuration didn't work, so try to go back to the old one.
|
||||
setStateInternal(tunnel, tunnel.getConfig(), State.UP);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> enumerate() {
|
||||
final List<String> output = new ArrayList<>();
|
||||
// Don't throw an exception here or nothing will show up in the UI.
|
||||
try {
|
||||
toolsInstaller.ensureToolsAvailable();
|
||||
if (rootShell.run(output, "wg show interfaces") != 0 || output.isEmpty())
|
||||
return Collections.emptySet();
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, "Unable to enumerate running tunnels", e);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
// wg puts all interface names on the same line. Split them into separate elements.
|
||||
return Stream.of(output.get(0).split(" ")).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState(final Tunnel tunnel) {
|
||||
return enumerate().contains(tunnel.getName()) ? State.UP : State.DOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statistics getStatistics(final Tunnel tunnel) {
|
||||
return new Statistics();
|
||||
}
|
||||
|
||||
@Override
|
||||
public State setState(final Tunnel tunnel, State state) throws Exception {
|
||||
final State originalState = getState(tunnel);
|
||||
if (state == State.TOGGLE)
|
||||
state = originalState == State.UP ? State.DOWN : State.UP;
|
||||
if (state == originalState)
|
||||
return originalState;
|
||||
Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state);
|
||||
toolsInstaller.ensureToolsAvailable();
|
||||
setStateInternal(tunnel, tunnel.getConfig(), state);
|
||||
return getState(tunnel);
|
||||
}
|
||||
|
||||
private void setStateInternal(final Tunnel tunnel, final Config config, final State state)
|
||||
throws Exception {
|
||||
final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf");
|
||||
try (FileOutputStream stream = new FileOutputStream(tempFile, false)) {
|
||||
stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
final String command = String.format("wg-quick %s '%s'",
|
||||
state.toString().toLowerCase(), tempFile.getAbsolutePath());
|
||||
final int result = rootShell.run(null, command);
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
tempFile.delete();
|
||||
if (result != 0)
|
||||
throw new Exception("Unable to configure tunnel (wg-quick returned " + result + ')');
|
||||
}
|
||||
|
||||
public static final class WgQuickChangeReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
Log.d(TAG, "Refreshing tunnel states");
|
||||
Application.getComponent().getTunnelManager().refreshTunnelStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.configStore;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.Application.ApplicationContext;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Set;
|
||||
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Configuration store that uses a {@code wg-quick}-style file for each configured tunnel.
|
||||
*/
|
||||
|
||||
public final class FileConfigStore implements ConfigStore {
|
||||
private static final String TAG = "WireGuard/" + FileConfigStore.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
|
||||
public FileConfigStore(@ApplicationContext final Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config create(final String name, final Config config) throws IOException {
|
||||
Log.d(TAG, "Creating configuration for tunnel " + name);
|
||||
final File file = fileFor(name);
|
||||
if (!file.createNewFile())
|
||||
throw new IOException("Configuration file " + file.getName() + " already exists");
|
||||
try (FileOutputStream stream = new FileOutputStream(file, false)) {
|
||||
stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(final String name) throws IOException {
|
||||
Log.d(TAG, "Deleting configuration for tunnel " + name);
|
||||
final File file = fileFor(name);
|
||||
if (!file.delete())
|
||||
throw new IOException("Cannot delete configuration file " + file.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> enumerate() {
|
||||
return Stream.of(context.fileList())
|
||||
.filter(name -> name.endsWith(".conf"))
|
||||
.map(name -> name.substring(0, name.length() - ".conf".length()))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
private File fileFor(final String name) {
|
||||
return new File(context.getFilesDir(), name + ".conf");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config load(final String name) throws IOException {
|
||||
try (FileInputStream stream = new FileInputStream(fileFor(name))) {
|
||||
return Config.from(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(final String name, final String replacement) throws IOException {
|
||||
Log.d(TAG, "Renaming configuration for tunnel " + name + " to " + replacement);
|
||||
final File file = fileFor(name);
|
||||
final File replacementFile = fileFor(replacement);
|
||||
if (!replacementFile.createNewFile())
|
||||
throw new IOException("Configuration for " + replacement + " already exists");
|
||||
if (!file.renameTo(replacementFile)) {
|
||||
if (!replacementFile.delete())
|
||||
Log.w(TAG, "Couldn't delete marker file for new name " + replacement);
|
||||
throw new IOException("Cannot rename configuration file " + file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config save(final String name, final Config config) throws IOException {
|
||||
Log.d(TAG, "Saving configuration for tunnel " + name);
|
||||
final File file = fileFor(name);
|
||||
if (!file.isFile())
|
||||
throw new FileNotFoundException("Configuration file " + file.getName() + " not found");
|
||||
try (FileOutputStream stream = new FileOutputStream(file, false)) {
|
||||
stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.databinding;
|
||||
|
||||
import android.databinding.BindingAdapter;
|
||||
import android.databinding.ObservableList;
|
||||
import android.databinding.adapters.ListenerUtil;
|
||||
import android.text.InputFilter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.util.Keyed;
|
||||
import com.wireguard.android.util.ObservableKeyedList;
|
||||
import com.wireguard.android.widget.ToggleSwitch;
|
||||
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
|
||||
|
||||
/**
|
||||
* Static methods for use by generated code in the Android data binding library.
|
||||
*/
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class BindingAdapters {
|
||||
private BindingAdapters() {
|
||||
// Prevent instantiation.
|
||||
}
|
||||
|
||||
@BindingAdapter("checked")
|
||||
public static void setChecked(final ToggleSwitch view, final boolean checked) {
|
||||
view.setCheckedInternal(checked);
|
||||
}
|
||||
|
||||
@BindingAdapter("filter")
|
||||
public static void setFilter(final TextView view, final InputFilter filter) {
|
||||
view.setFilters(new InputFilter[]{filter});
|
||||
}
|
||||
|
||||
@BindingAdapter({"items", "layout"})
|
||||
public static <E> void setItems(final LinearLayout view,
|
||||
final ObservableList<E> oldList, final int oldLayoutId,
|
||||
final ObservableList<E> newList, final int newLayoutId) {
|
||||
if (oldList == newList && oldLayoutId == newLayoutId)
|
||||
return;
|
||||
ItemChangeListener<E> listener = ListenerUtil.getListener(view, R.id.item_change_listener);
|
||||
// If the layout changes, any existing listener must be replaced.
|
||||
if (listener != null && oldList != null && oldLayoutId != newLayoutId) {
|
||||
listener.setList(null);
|
||||
listener = null;
|
||||
// Stop tracking the old listener.
|
||||
ListenerUtil.trackListener(view, null, R.id.item_change_listener);
|
||||
}
|
||||
// Avoid adding a listener when there is no new list or layout.
|
||||
if (newList == null || newLayoutId == 0)
|
||||
return;
|
||||
if (listener == null) {
|
||||
listener = new ItemChangeListener<>(view, newLayoutId);
|
||||
ListenerUtil.trackListener(view, listener, R.id.item_change_listener);
|
||||
}
|
||||
// Either the list changed, or this is an entirely new listener because the layout changed.
|
||||
listener.setList(newList);
|
||||
}
|
||||
|
||||
@BindingAdapter({"items", "layout"})
|
||||
public static <K, E extends Keyed<? extends K>>
|
||||
void setItems(final ListView view,
|
||||
final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
|
||||
final ObservableKeyedList<K, E> newList, final int newLayoutId) {
|
||||
if (oldList == newList && oldLayoutId == newLayoutId)
|
||||
return;
|
||||
// The ListAdapter interface is not generic, so this cannot be checked.
|
||||
@SuppressWarnings("unchecked") ObservableKeyedListAdapter<K, E> adapter =
|
||||
(ObservableKeyedListAdapter<K, E>) view.getAdapter();
|
||||
// If the layout changes, any existing adapter must be replaced.
|
||||
if (adapter != null && oldList != null && oldLayoutId != newLayoutId) {
|
||||
adapter.setList(null);
|
||||
adapter = null;
|
||||
}
|
||||
// Avoid setting an adapter when there is no new list or layout.
|
||||
if (newList == null || newLayoutId == 0)
|
||||
return;
|
||||
if (adapter == null) {
|
||||
adapter = new ObservableKeyedListAdapter<>(view.getContext(), newLayoutId, newList);
|
||||
view.setAdapter(adapter);
|
||||
}
|
||||
// Either the list changed, or this is an entirely new listener because the layout changed.
|
||||
adapter.setList(newList);
|
||||
}
|
||||
|
||||
@BindingAdapter("onBeforeCheckedChanged")
|
||||
public static void setOnBeforeCheckedChanged(final ToggleSwitch view,
|
||||
final OnBeforeCheckedChangeListener listener) {
|
||||
view.setOnBeforeCheckedChangeListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.databinding;
|
||||
|
||||
import android.databinding.DataBindingUtil;
|
||||
import android.databinding.ObservableList;
|
||||
import android.databinding.ViewDataBinding;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.wireguard.android.BR;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Helper class for binding an ObservableList to the children of a ViewGroup.
|
||||
*/
|
||||
|
||||
class ItemChangeListener<T> {
|
||||
private final OnListChangedCallback<T> callback = new OnListChangedCallback<>(this);
|
||||
private final ViewGroup container;
|
||||
private final int layoutId;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private ObservableList<T> list;
|
||||
|
||||
ItemChangeListener(final ViewGroup container, final int layoutId) {
|
||||
this.container = container;
|
||||
this.layoutId = layoutId;
|
||||
layoutInflater = LayoutInflater.from(container.getContext());
|
||||
}
|
||||
|
||||
private View getView(final int position, final View convertView) {
|
||||
ViewDataBinding binding = DataBindingUtil.getBinding(convertView);
|
||||
if (binding == null)
|
||||
binding = DataBindingUtil.inflate(layoutInflater, layoutId, container, false);
|
||||
binding.setVariable(BR.collection, list);
|
||||
binding.setVariable(BR.item, list.get(position));
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
void setList(final ObservableList<T> newList) {
|
||||
if (list != null)
|
||||
list.removeOnListChangedCallback(callback);
|
||||
list = newList;
|
||||
if (list != null) {
|
||||
list.addOnListChangedCallback(callback);
|
||||
callback.onChanged(list);
|
||||
} else {
|
||||
container.removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OnListChangedCallback<T>
|
||||
extends ObservableList.OnListChangedCallback<ObservableList<T>> {
|
||||
|
||||
private final WeakReference<ItemChangeListener<T>> weakListener;
|
||||
|
||||
private OnListChangedCallback(final ItemChangeListener<T> listener) {
|
||||
weakListener = new WeakReference<>(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(final ObservableList<T> sender) {
|
||||
final ItemChangeListener<T> listener = weakListener.get();
|
||||
if (listener != null) {
|
||||
// TODO: recycle views
|
||||
listener.container.removeAllViews();
|
||||
for (int i = 0; i < sender.size(); ++i)
|
||||
listener.container.addView(listener.getView(i, null));
|
||||
} else {
|
||||
sender.removeOnListChangedCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(final ObservableList<T> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
final ItemChangeListener<T> listener = weakListener.get();
|
||||
if (listener != null) {
|
||||
for (int i = positionStart; i < positionStart + itemCount; ++i) {
|
||||
final View child = listener.container.getChildAt(i);
|
||||
listener.container.removeViewAt(i);
|
||||
listener.container.addView(listener.getView(i, child));
|
||||
}
|
||||
} else {
|
||||
sender.removeOnListChangedCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(final ObservableList<T> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
final ItemChangeListener<T> listener = weakListener.get();
|
||||
if (listener != null) {
|
||||
for (int i = positionStart; i < positionStart + itemCount; ++i)
|
||||
listener.container.addView(listener.getView(i, null));
|
||||
} else {
|
||||
sender.removeOnListChangedCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(final ObservableList<T> sender, final int fromPosition,
|
||||
final int toPosition, final int itemCount) {
|
||||
final ItemChangeListener<T> listener = weakListener.get();
|
||||
if (listener != null) {
|
||||
final View[] views = new View[itemCount];
|
||||
for (int i = 0; i < itemCount; ++i)
|
||||
views[i] = listener.container.getChildAt(fromPosition + i);
|
||||
listener.container.removeViews(fromPosition, itemCount);
|
||||
for (int i = 0; i < itemCount; ++i)
|
||||
listener.container.addView(views[i], toPosition + i);
|
||||
} else {
|
||||
sender.removeOnListChangedCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(final ObservableList<T> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
final ItemChangeListener<T> listener = weakListener.get();
|
||||
if (listener != null) {
|
||||
listener.container.removeViews(positionStart, itemCount);
|
||||
} else {
|
||||
sender.removeOnListChangedCallback(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.databinding;
|
||||
|
||||
import android.content.Context;
|
||||
import android.databinding.DataBindingUtil;
|
||||
import android.databinding.ObservableList;
|
||||
import android.databinding.ViewDataBinding;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
import com.wireguard.android.BR;
|
||||
import com.wireguard.android.util.Keyed;
|
||||
import com.wireguard.android.util.ObservableKeyedList;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* A generic {@code ListAdapter} backed by a {@code ObservableKeyedList}.
|
||||
*/
|
||||
|
||||
class ObservableKeyedListAdapter<K, E extends Keyed<? extends K>> extends BaseAdapter {
|
||||
private final OnListChangedCallback<E> callback = new OnListChangedCallback<>(this);
|
||||
private final int layoutId;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private ObservableKeyedList<K, E> list;
|
||||
|
||||
ObservableKeyedListAdapter(final Context context, final int layoutId,
|
||||
final ObservableKeyedList<K, E> list) {
|
||||
this.layoutId = layoutId;
|
||||
layoutInflater = LayoutInflater.from(context);
|
||||
setList(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return list != null ? list.size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getItem(final int position) {
|
||||
if (list == null || position < 0 || position >= list.size())
|
||||
return null;
|
||||
return list.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(final int position) {
|
||||
final K key = getKey(position);
|
||||
return key != null ? key.hashCode() : -1;
|
||||
}
|
||||
|
||||
private K getKey(final int position) {
|
||||
final E item = getItem(position);
|
||||
return item != null ? item.getKey() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, final View convertView, final ViewGroup parent) {
|
||||
ViewDataBinding binding = DataBindingUtil.getBinding(convertView);
|
||||
if (binding == null)
|
||||
binding = DataBindingUtil.inflate(layoutInflater, layoutId, parent, false);
|
||||
binding.setVariable(BR.collection, list);
|
||||
binding.setVariable(BR.key, getKey(position));
|
||||
binding.setVariable(BR.item, getItem(position));
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setList(final ObservableKeyedList<K, E> newList) {
|
||||
if (list != null)
|
||||
list.removeOnListChangedCallback(callback);
|
||||
list = newList;
|
||||
if (list != null) {
|
||||
list.addOnListChangedCallback(callback);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private static final class OnListChangedCallback<E extends Keyed<?>>
|
||||
extends ObservableList.OnListChangedCallback<ObservableList<E>> {
|
||||
|
||||
private final WeakReference<ObservableKeyedListAdapter<?, E>> weakAdapter;
|
||||
|
||||
private OnListChangedCallback(final ObservableKeyedListAdapter<?, E> adapter) {
|
||||
weakAdapter = new WeakReference<>(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(final ObservableList<E> sender) {
|
||||
final ObservableKeyedListAdapter adapter = weakAdapter.get();
|
||||
if (adapter != null)
|
||||
adapter.notifyDataSetChanged();
|
||||
else
|
||||
sender.removeOnListChangedCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(final ObservableList<E> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
onChanged(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(final ObservableList<E> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
onChanged(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(final ObservableList<E> sender, final int fromPosition,
|
||||
final int toPosition, final int itemCount) {
|
||||
onChanged(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(final ObservableList<E> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
onChanged(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import com.wireguard.android.activity.BaseActivity;
|
||||
import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
|
||||
/**
|
||||
* Base class for fragments that need to know the currently-selected tunnel. Only does anything when
|
||||
* attached to a {@code BaseActivity}.
|
||||
*/
|
||||
|
||||
public abstract class BaseFragment extends Fragment implements OnSelectedTunnelChangedListener {
|
||||
private BaseActivity activity;
|
||||
|
||||
protected Tunnel getSelectedTunnel() {
|
||||
return activity != null ? activity.getSelectedTunnel() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof BaseActivity) {
|
||||
activity = (BaseActivity) context;
|
||||
activity.addOnSelectedTunnelChangedListener(this);
|
||||
} else {
|
||||
activity = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
if (activity != null)
|
||||
activity.removeOnSelectedTunnelChangedListener(this);
|
||||
activity = null;
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
protected void setSelectedTunnel(final Tunnel tunnel) {
|
||||
if (activity != null)
|
||||
activity.setSelectedTunnel(tunnel);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.databinding.DataBindingUtil;
|
||||
import android.databinding.ViewDataBinding;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
|
||||
import com.wireguard.android.databinding.TunnelListItemBinding;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
|
||||
/**
|
||||
* Helper method shared by TunnelListFragment and TunnelDetailFragment.
|
||||
*/
|
||||
|
||||
public final class TunnelController {
|
||||
private static final String TAG = "WireGuard/" + TunnelController.class.getSimpleName();
|
||||
|
||||
private TunnelController() {
|
||||
// Prevent instantiation.
|
||||
}
|
||||
|
||||
public static void setTunnelState(final View view, final boolean checked) {
|
||||
final ViewDataBinding binding = DataBindingUtil.findBinding(view);
|
||||
final Tunnel tunnel;
|
||||
if (binding instanceof TunnelDetailFragmentBinding)
|
||||
tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel();
|
||||
else if (binding instanceof TunnelListItemBinding)
|
||||
tunnel = ((TunnelListItemBinding) binding).getItem();
|
||||
else
|
||||
tunnel = null;
|
||||
if (tunnel == null) {
|
||||
Log.e(TAG, "setChecked() from a null tunnel", new IllegalStateException("No tunnel"));
|
||||
return;
|
||||
}
|
||||
tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> {
|
||||
if (throwable == null)
|
||||
return;
|
||||
final Context context = view.getContext();
|
||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
final int messageResId = checked ? R.string.error_up : R.string.error_down;
|
||||
final String message = context.getString(messageResId, error);
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
|
||||
Log.e(TAG, message, throwable);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
/**
|
||||
* Fragment that shows details about a specific tunnel.
|
||||
*/
|
||||
|
||||
public class TunnelDetailFragment extends BaseFragment {
|
||||
private TunnelDetailFragmentBinding binding;
|
||||
|
||||
private void onConfigLoaded(final String name, final Config config) {
|
||||
binding.setConfig(new Config.Observable(config, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.tunnel_detail, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = TunnelDetailFragmentBinding.inflate(inflater, container, false);
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
binding = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
if (binding == null)
|
||||
return;
|
||||
binding.setTunnel(newTunnel);
|
||||
if (newTunnel == null)
|
||||
binding.setConfig(null);
|
||||
else
|
||||
newTunnel.getConfigAsync().thenAccept(a -> onConfigLoaded(newTunnel.getName(), a));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(final Bundle savedInstanceState) {
|
||||
onSelectedTunnelChanged(null, getSelectedTunnel());
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.databinding.TunnelEditorFragmentBinding;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
/**
|
||||
* Fragment for editing a WireGuard configuration.
|
||||
*/
|
||||
|
||||
public class TunnelEditorFragment extends BaseFragment {
|
||||
private static final String KEY_LOCAL_CONFIG = "local_config";
|
||||
private static final String KEY_ORIGINAL_NAME = "original_name";
|
||||
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
|
||||
|
||||
private TunnelEditorFragmentBinding binding;
|
||||
private Tunnel tunnel;
|
||||
|
||||
private void onConfigLoaded(final String name, final Config config) {
|
||||
binding.setConfig(new Config.Observable(config, name));
|
||||
}
|
||||
|
||||
private void onConfigSaved(final Tunnel savedTunnel,
|
||||
final Throwable throwable) {
|
||||
final String message;
|
||||
if (throwable == null) {
|
||||
message = getString(R.string.config_save_success, savedTunnel.getName());
|
||||
Log.d(TAG, message);
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
onFinished();
|
||||
} else {
|
||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
message = getString(R.string.config_save_error, savedTunnel.getName(), error);
|
||||
Log.e(TAG, message, throwable);
|
||||
if (binding != null) {
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.config_editor, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = TunnelEditorFragmentBinding.inflate(inflater, container, false);
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
binding = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void onFinished() {
|
||||
// Hide the keyboard; it rarely goes away on its own.
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null) return;
|
||||
final View focusedView = activity.getCurrentFocus();
|
||||
if (focusedView != null) {
|
||||
final Object service = activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
final InputMethodManager inputManager = (InputMethodManager) service;
|
||||
if (inputManager != null)
|
||||
inputManager.hideSoftInputFromWindow(focusedView.getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
}
|
||||
// Tell the activity to finish itself or go back to the detail view.
|
||||
getActivity().runOnUiThread(() -> {
|
||||
// The selected tunnel has to actually change, but we have to remember this one.
|
||||
final Tunnel savedTunnel = tunnel;
|
||||
if (savedTunnel == getSelectedTunnel())
|
||||
setSelectedTunnel(null);
|
||||
setSelectedTunnel(savedTunnel);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_action_save:
|
||||
final Config newConfig = new Config();
|
||||
try {
|
||||
binding.getConfig().commitData(newConfig);
|
||||
} catch (final Exception e) {
|
||||
final String error = ExceptionLoggers.unwrapMessage(e);
|
||||
final String tunnelName = tunnel == null ? binding.getConfig().getName() : tunnel.getName();
|
||||
final String message = getString(R.string.config_save_error, tunnelName, error);
|
||||
Log.e(TAG, message, e);
|
||||
Snackbar.make(binding.mainContainer, error, Snackbar.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
if (tunnel == null) {
|
||||
Log.d(TAG, "Attempting to create new tunnel " + binding.getConfig().getName());
|
||||
final TunnelManager manager = Application.getComponent().getTunnelManager();
|
||||
manager.create(binding.getConfig().getName(), newConfig)
|
||||
.whenComplete(this::onTunnelCreated);
|
||||
} else if (!tunnel.getName().equals(binding.getConfig().getName())) {
|
||||
Log.d(TAG, "Attempting to rename tunnel to " + binding.getConfig().getName());
|
||||
tunnel.setName(binding.getConfig().getName())
|
||||
.whenComplete((a, b) -> onTunnelRenamed(tunnel, newConfig, b));
|
||||
} else {
|
||||
Log.d(TAG, "Attempting to save config of " + tunnel.getName());
|
||||
tunnel.setConfig(newConfig)
|
||||
.whenComplete((a, b) -> onConfigSaved(tunnel, b));
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||
outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
|
||||
outState.putString(KEY_ORIGINAL_NAME, tunnel == null ? null : tunnel.getName());
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
tunnel = newTunnel;
|
||||
if (binding == null)
|
||||
return;
|
||||
binding.setConfig(new Config.Observable(null, null));
|
||||
if (tunnel != null)
|
||||
tunnel.getConfigAsync().thenAccept(a -> onConfigLoaded(tunnel.getName(), a));
|
||||
}
|
||||
|
||||
private void onTunnelCreated(final Tunnel newTunnel, final Throwable throwable) {
|
||||
final String message;
|
||||
if (throwable == null) {
|
||||
tunnel = newTunnel;
|
||||
message = getString(R.string.tunnel_create_success, tunnel.getName());
|
||||
Log.d(TAG, message);
|
||||
onFinished();
|
||||
} else {
|
||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
message = getString(R.string.tunnel_create_error, error);
|
||||
Log.e(TAG, message, throwable);
|
||||
if (binding != null) {
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTunnelRenamed(final Tunnel renamedTunnel, final Config newConfig,
|
||||
final Throwable throwable) {
|
||||
final String message;
|
||||
if (throwable == null) {
|
||||
message = getString(R.string.tunnel_rename_success, renamedTunnel.getName());
|
||||
Log.d(TAG, message);
|
||||
// Now save the rest of configuration changes.
|
||||
Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel.getName());
|
||||
renamedTunnel.setConfig(newConfig).whenComplete((a, b) -> onConfigSaved(renamedTunnel, b));
|
||||
} else {
|
||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
message = getString(R.string.tunnel_rename_error, error);
|
||||
Log.e(TAG, message, throwable);
|
||||
if (binding != null) {
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(final Bundle savedInstanceState) {
|
||||
if (savedInstanceState == null) {
|
||||
onSelectedTunnelChanged(null, getSelectedTunnel());
|
||||
} else {
|
||||
tunnel = getSelectedTunnel();
|
||||
final Config.Observable config = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG);
|
||||
final String originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME);
|
||||
if (tunnel != null && !tunnel.getName().equals(originalName))
|
||||
onSelectedTunnelChanged(null, tunnel);
|
||||
else
|
||||
binding.setConfig(config);
|
||||
}
|
||||
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
}
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.Application.ApplicationComponent;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.activity.TunnelCreatorActivity;
|
||||
import com.wireguard.android.databinding.TunnelListFragmentBinding;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.AsyncWorker;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.IntStream;
|
||||
import java9.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Fragment containing a list of known WireGuard tunnels. It allows creating and deleting tunnels.
|
||||
*/
|
||||
|
||||
public class TunnelListFragment extends BaseFragment {
|
||||
private static final int REQUEST_IMPORT = 1;
|
||||
private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
|
||||
|
||||
private final MultiChoiceModeListener actionModeListener = new ActionModeListener();
|
||||
private final ListViewCallbacks listViewCallbacks = new ListViewCallbacks();
|
||||
private ActionMode actionMode;
|
||||
private AsyncWorker asyncWorker;
|
||||
private TunnelListFragmentBinding binding;
|
||||
private TunnelManager tunnelManager;
|
||||
|
||||
public boolean collapseActionMenu() {
|
||||
if (binding.createMenu.isExpanded()) {
|
||||
binding.createMenu.collapse();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void importTunnel(final Uri uri) {
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null)
|
||||
return;
|
||||
final ContentResolver contentResolver = activity.getContentResolver();
|
||||
|
||||
final Collection<CompletableFuture<Tunnel>> futureTunnels = new ArrayList<>();
|
||||
final List<Throwable> throwables = new ArrayList<>();
|
||||
asyncWorker.supplyAsync(() -> {
|
||||
final String[] columns = {OpenableColumns.DISPLAY_NAME};
|
||||
String name = null;
|
||||
try (Cursor cursor = contentResolver.query(uri, columns,
|
||||
null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst() && !cursor.isNull(0))
|
||||
name = cursor.getString(0);
|
||||
}
|
||||
if (name == null)
|
||||
name = Uri.decode(uri.getLastPathSegment());
|
||||
int idx = name.lastIndexOf('/');
|
||||
if (idx >= 0) {
|
||||
if (idx >= name.length() - 1)
|
||||
throw new IllegalArgumentException("Illegal file name: " + name);
|
||||
name = name.substring(idx + 1);
|
||||
}
|
||||
boolean isZip = name.toLowerCase().endsWith(".zip");
|
||||
if (name.toLowerCase().endsWith(".conf"))
|
||||
name = name.substring(0, name.length() - ".conf".length());
|
||||
else if (!isZip)
|
||||
throw new IllegalArgumentException("File must be .conf or .zip");
|
||||
|
||||
if (isZip) {
|
||||
try (ZipInputStream zip = new ZipInputStream(contentResolver.openInputStream(uri))) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(zip, StandardCharsets.UTF_8));
|
||||
ZipEntry entry;
|
||||
while ((entry = zip.getNextEntry()) != null) {
|
||||
if (entry.isDirectory())
|
||||
continue;
|
||||
name = entry.getName();
|
||||
idx = name.lastIndexOf('/');
|
||||
if (idx >= 0) {
|
||||
if (idx >= name.length() - 1)
|
||||
continue;
|
||||
name = name.substring(name.lastIndexOf('/') + 1);
|
||||
}
|
||||
if (name.toLowerCase().endsWith(".conf"))
|
||||
name = name.substring(0, name.length() - ".conf".length());
|
||||
else
|
||||
continue;
|
||||
Config config = null;
|
||||
try {
|
||||
config = Config.from(reader);
|
||||
} catch (Exception e) {
|
||||
throwables.add(e);
|
||||
}
|
||||
if (config != null)
|
||||
futureTunnels.add(tunnelManager.create(name, config).toCompletableFuture());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
futureTunnels.add(tunnelManager.create(name,
|
||||
Config.from(contentResolver.openInputStream(uri))).toCompletableFuture());
|
||||
}
|
||||
|
||||
if (futureTunnels.isEmpty()) {
|
||||
if (throwables.size() == 1)
|
||||
throw throwables.get(0);
|
||||
else if (throwables.isEmpty())
|
||||
throw new IllegalArgumentException("No configurations found");
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(futureTunnels.toArray(new CompletableFuture[futureTunnels.size()]));
|
||||
}).whenComplete((future, exception) -> {
|
||||
if (exception != null) {
|
||||
onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception));
|
||||
} else {
|
||||
future.whenComplete((ignored1, ignored2) -> {
|
||||
final List<Tunnel> tunnels = new ArrayList<>(futureTunnels.size());
|
||||
for (final CompletableFuture<Tunnel> futureTunnel : futureTunnels) {
|
||||
Tunnel tunnel = null;
|
||||
try {
|
||||
tunnel = futureTunnel.getNow(null);
|
||||
} catch (final Exception e) {
|
||||
throwables.add(e);
|
||||
}
|
||||
if (tunnel != null)
|
||||
tunnels.add(tunnel);
|
||||
}
|
||||
onTunnelImportFinished(tunnels, throwables);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_IMPORT:
|
||||
if (resultCode == Activity.RESULT_OK)
|
||||
importTunnel(data.getData());
|
||||
return;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final ApplicationComponent applicationComponent = Application.getComponent();
|
||||
asyncWorker = applicationComponent.getAsyncWorker();
|
||||
tunnelManager = applicationComponent.getTunnelManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = TunnelListFragmentBinding.inflate(inflater, container, false);
|
||||
binding.tunnelList.setMultiChoiceModeListener(actionModeListener);
|
||||
binding.tunnelList.setOnItemClickListener(listViewCallbacks);
|
||||
binding.tunnelList.setOnItemLongClickListener(listViewCallbacks);
|
||||
binding.tunnelList.setOnTouchListener(listViewCallbacks);
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
binding = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
|
||||
startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
|
||||
if (binding != null)
|
||||
binding.createMenu.collapse();
|
||||
}
|
||||
|
||||
public void onRequestImportConfig(@SuppressWarnings("unused") final View view) {
|
||||
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, REQUEST_IMPORT);
|
||||
if (binding != null)
|
||||
binding.createMenu.collapse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private void onTunnelDeletionFinished(final Integer count, final Throwable throwable) {
|
||||
final String message;
|
||||
if (throwable == null) {
|
||||
message = getResources().getQuantityString(R.plurals.delete_success, count, count);
|
||||
} else {
|
||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
message = getResources().getQuantityString(R.plurals.delete_error, count, count, error);
|
||||
Log.e(TAG, message, throwable);
|
||||
}
|
||||
if (binding != null) {
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void onTunnelImportFinished(final List<Tunnel> tunnels, final Collection<Throwable> throwables) {
|
||||
String message = null;
|
||||
|
||||
for (final Throwable throwable : throwables) {
|
||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
message = getString(R.string.import_error, error);
|
||||
Log.e(TAG, message, throwable);
|
||||
}
|
||||
|
||||
if (tunnels.size() == 1 && throwables.isEmpty())
|
||||
message = getString(R.string.import_success, tunnels.get(0).getName());
|
||||
else if (tunnels.isEmpty() && throwables.size() == 1)
|
||||
/* Use the exception message from above. */ ;
|
||||
else if (throwables.isEmpty())
|
||||
message = getResources().getQuantityString(R.plurals.import_total_success,
|
||||
tunnels.size(), tunnels.size());
|
||||
else if (!throwables.isEmpty())
|
||||
message = getResources().getQuantityString(R.plurals.import_partial_success,
|
||||
tunnels.size() + throwables.size(),
|
||||
tunnels.size(), tunnels.size() + throwables.size());
|
||||
|
||||
if (binding != null) {
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(final Bundle savedInstanceState) {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
binding.setFragment(this);
|
||||
binding.setTunnels(tunnelManager.getTunnels());
|
||||
}
|
||||
|
||||
private final class ActionModeListener implements MultiChoiceModeListener {
|
||||
private Resources resources;
|
||||
private AbsListView tunnelList;
|
||||
|
||||
private IntStream getCheckedPositions() {
|
||||
final SparseBooleanArray checkedItemPositions = tunnelList.getCheckedItemPositions();
|
||||
return IntStream.range(0, checkedItemPositions.size())
|
||||
.filter(checkedItemPositions::valueAt)
|
||||
.map(checkedItemPositions::keyAt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_action_delete:
|
||||
// Must operate in two steps: positions change once we start deleting things.
|
||||
final List<Tunnel> tunnelsToDelete = getCheckedPositions()
|
||||
.mapToObj(pos -> (Tunnel) tunnelList.getItemAtPosition(pos))
|
||||
.collect(Collectors.toList());
|
||||
final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete)
|
||||
.map(Tunnel::delete)
|
||||
.toArray(CompletableFuture[]::new);
|
||||
CompletableFuture.allOf(futures)
|
||||
.thenApply(x -> futures.length)
|
||||
.whenComplete(TunnelListFragment.this::onTunnelDeletionFinished);
|
||||
mode.finish();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
|
||||
actionMode = mode;
|
||||
if (getActivity() != null)
|
||||
resources = getActivity().getResources();
|
||||
tunnelList = binding.tunnelList;
|
||||
mode.getMenuInflater().inflate(R.menu.tunnel_list_action_mode, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(final ActionMode mode) {
|
||||
actionMode = null;
|
||||
resources = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(final ActionMode mode, final int position,
|
||||
final long id, final boolean checked) {
|
||||
updateTitle(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
|
||||
updateTitle(mode);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateTitle(final ActionMode mode) {
|
||||
final int count = (int) getCheckedPositions().count();
|
||||
mode.setTitle(resources.getQuantityString(R.plurals.delete_title, count, count));
|
||||
}
|
||||
}
|
||||
|
||||
private final class ListViewCallbacks
|
||||
implements OnItemClickListener, OnItemLongClickListener, OnTouchListener {
|
||||
@Override
|
||||
public void onItemClick(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
setSelectedTunnel((Tunnel) parent.getItemAtPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
if (actionMode != null)
|
||||
return false;
|
||||
if (binding != null)
|
||||
binding.tunnelList.setItemChecked(position, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public boolean onTouch(final View view, final MotionEvent motionEvent) {
|
||||
if (binding != null)
|
||||
binding.createMenu.collapse();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.model;
|
||||
|
||||
import android.databinding.BaseObservable;
|
||||
import android.databinding.Bindable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.wireguard.android.BR;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.android.util.Keyed;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletionStage;
|
||||
|
||||
/**
|
||||
* Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
|
||||
*/
|
||||
|
||||
public class Tunnel extends BaseObservable implements Keyed<String> {
|
||||
public static final int NAME_MAX_LENGTH = 15;
|
||||
private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}");
|
||||
|
||||
private final TunnelManager manager;
|
||||
private Config config;
|
||||
private String name;
|
||||
private State state;
|
||||
private Statistics statistics;
|
||||
|
||||
Tunnel(@NonNull final TunnelManager manager, @NonNull final String name,
|
||||
@Nullable final Config config, @NonNull final State state) {
|
||||
this.manager = manager;
|
||||
this.name = name;
|
||||
this.config = config;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public static boolean isNameInvalid(@NonNull final CharSequence name) {
|
||||
return !NAME_PATTERN.matcher(name).matches();
|
||||
}
|
||||
|
||||
public CompletionStage<Void> delete() {
|
||||
return manager.delete(this);
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public Config getConfig() {
|
||||
if (config == null)
|
||||
manager.getTunnelConfig(this).whenComplete(ExceptionLoggers.E);
|
||||
return config;
|
||||
}
|
||||
|
||||
public CompletionStage<Config> getConfigAsync() {
|
||||
if (config == null)
|
||||
return manager.getTunnelConfig(this);
|
||||
return CompletableFuture.completedFuture(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public CompletionStage<State> getStateAsync() {
|
||||
return manager.getTunnelState(this);
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public Statistics getStatistics() {
|
||||
// FIXME: Check age of statistics.
|
||||
if (statistics == null)
|
||||
manager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E);
|
||||
return statistics;
|
||||
}
|
||||
|
||||
public CompletionStage<Statistics> getStatisticsAsync() {
|
||||
// FIXME: Check age of statistics.
|
||||
if (statistics == null)
|
||||
return manager.getTunnelStatistics(this);
|
||||
return CompletableFuture.completedFuture(statistics);
|
||||
}
|
||||
|
||||
Config onConfigChanged(final Config config) {
|
||||
this.config = config;
|
||||
notifyPropertyChanged(BR.config);
|
||||
return config;
|
||||
}
|
||||
|
||||
public String onNameChanged(final String name) {
|
||||
this.name = name;
|
||||
notifyPropertyChanged(BR.name);
|
||||
return name;
|
||||
}
|
||||
|
||||
State onStateChanged(final State state) {
|
||||
if (state != State.UP)
|
||||
onStatisticsChanged(null);
|
||||
this.state = state;
|
||||
notifyPropertyChanged(BR.state);
|
||||
return state;
|
||||
}
|
||||
|
||||
Statistics onStatisticsChanged(final Statistics statistics) {
|
||||
this.statistics = statistics;
|
||||
notifyPropertyChanged(BR.statistics);
|
||||
return statistics;
|
||||
}
|
||||
|
||||
public CompletionStage<Config> setConfig(@NonNull final Config config) {
|
||||
if (!config.equals(this.config))
|
||||
return manager.setTunnelConfig(this, config);
|
||||
return CompletableFuture.completedFuture(this.config);
|
||||
}
|
||||
|
||||
public CompletionStage<String> setName(@NonNull final String name) {
|
||||
if (!name.equals(this.name))
|
||||
return manager.setTunnelName(this, name);
|
||||
return CompletableFuture.completedFuture(this.name);
|
||||
}
|
||||
|
||||
public CompletionStage<State> setState(@NonNull final State state) {
|
||||
if (state != this.state)
|
||||
return manager.setTunnelState(this, state);
|
||||
return CompletableFuture.completedFuture(this.state);
|
||||
}
|
||||
|
||||
public enum State {
|
||||
DOWN,
|
||||
TOGGLE,
|
||||
UP;
|
||||
|
||||
public static State of(final boolean running) {
|
||||
return running ? UP : DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Statistics extends BaseObservable {
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.model;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.databinding.BaseObservable;
|
||||
import android.databinding.Bindable;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.wireguard.android.Application.ApplicationScope;
|
||||
import com.wireguard.android.BR;
|
||||
import com.wireguard.android.backend.Backend;
|
||||
import com.wireguard.android.configStore.ConfigStore;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.model.Tunnel.Statistics;
|
||||
import com.wireguard.android.util.AsyncWorker;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.android.util.ObservableKeyedList;
|
||||
import com.wireguard.android.util.ObservableSortedKeyedArrayList;
|
||||
import com.wireguard.android.util.ObservableSortedKeyedList;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java9.util.Comparators;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletionStage;
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Maintains and mediates changes to the set of available WireGuard tunnels,
|
||||
*/
|
||||
|
||||
@ApplicationScope
|
||||
public final class TunnelManager extends BaseObservable {
|
||||
private static final Comparator<String> COMPARATOR = Comparators.<String>thenComparing(
|
||||
String.CASE_INSENSITIVE_ORDER, Comparators.naturalOrder());
|
||||
private static final String KEY_LAST_USED_TUNNEL = "last_used_tunnel";
|
||||
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
|
||||
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
|
||||
|
||||
private final AsyncWorker asyncWorker;
|
||||
private final Backend backend;
|
||||
private final ConfigStore configStore;
|
||||
private final SharedPreferences preferences;
|
||||
private final ObservableSortedKeyedList<String, Tunnel> tunnels =
|
||||
new ObservableSortedKeyedArrayList<>(COMPARATOR);
|
||||
private Tunnel lastUsedTunnel;
|
||||
|
||||
@Inject
|
||||
public TunnelManager(final AsyncWorker asyncWorker, final Backend backend,
|
||||
final ConfigStore configStore, final SharedPreferences preferences) {
|
||||
this.asyncWorker = asyncWorker;
|
||||
this.backend = backend;
|
||||
this.configStore = configStore;
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
private Tunnel addToList(final String name, final Config config, final State state) {
|
||||
final Tunnel tunnel = new Tunnel(this, name, config, state);
|
||||
tunnels.add(tunnel);
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
public CompletionStage<Tunnel> create(@NonNull final String name, final Config config) {
|
||||
if (Tunnel.isNameInvalid(name))
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name"));
|
||||
if (tunnels.containsKey(name)) {
|
||||
final String message = "Tunnel " + name + " already exists";
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(message));
|
||||
}
|
||||
return asyncWorker.supplyAsync(() -> configStore.create(name, config))
|
||||
.thenApply(savedConfig -> addToList(name, savedConfig, State.DOWN));
|
||||
}
|
||||
|
||||
CompletionStage<Void> delete(final Tunnel tunnel) {
|
||||
final State originalState = tunnel.getState();
|
||||
final boolean wasLastUsed = tunnel == lastUsedTunnel;
|
||||
// Make sure nothing touches the tunnel.
|
||||
if (wasLastUsed)
|
||||
setLastUsedTunnel(null);
|
||||
tunnels.remove(tunnel);
|
||||
return asyncWorker.runAsync(() -> {
|
||||
if (originalState == State.UP)
|
||||
backend.setState(tunnel, State.DOWN);
|
||||
try {
|
||||
configStore.delete(tunnel.getName());
|
||||
} catch (final Exception e) {
|
||||
if (originalState == State.UP)
|
||||
backend.setState(tunnel, State.UP);
|
||||
// Re-throw the exception to fail the completion.
|
||||
throw e;
|
||||
}
|
||||
}).whenComplete((x, e) -> {
|
||||
if (e == null)
|
||||
return;
|
||||
// Failure, put the tunnel back.
|
||||
tunnels.add(tunnel);
|
||||
if (wasLastUsed)
|
||||
setLastUsedTunnel(tunnel);
|
||||
});
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public Tunnel getLastUsedTunnel() {
|
||||
return lastUsedTunnel;
|
||||
}
|
||||
|
||||
CompletionStage<Config> getTunnelConfig(final Tunnel tunnel) {
|
||||
return asyncWorker.supplyAsync(() -> configStore.load(tunnel.getName()))
|
||||
.thenApply(tunnel::onConfigChanged);
|
||||
}
|
||||
|
||||
CompletionStage<State> getTunnelState(final Tunnel tunnel) {
|
||||
return asyncWorker.supplyAsync(() -> backend.getState(tunnel))
|
||||
.thenApply(tunnel::onStateChanged);
|
||||
}
|
||||
|
||||
CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
|
||||
return asyncWorker.supplyAsync(() -> backend.getStatistics(tunnel))
|
||||
.thenApply(tunnel::onStatisticsChanged);
|
||||
}
|
||||
|
||||
public ObservableKeyedList<String, Tunnel> getTunnels() {
|
||||
return tunnels;
|
||||
}
|
||||
|
||||
public void onCreate() {
|
||||
asyncWorker.supplyAsync(configStore::enumerate)
|
||||
.thenAcceptBoth(asyncWorker.supplyAsync(backend::enumerate), this::onTunnelsLoaded)
|
||||
.whenComplete(ExceptionLoggers.E);
|
||||
}
|
||||
|
||||
private void onTunnelsLoaded(final Iterable<String> present, final Collection<String> running) {
|
||||
for (final String name : present)
|
||||
addToList(name, null, running.contains(name) ? State.UP : State.DOWN);
|
||||
final String lastUsedName = preferences.getString(KEY_LAST_USED_TUNNEL, null);
|
||||
if (lastUsedName != null)
|
||||
setLastUsedTunnel(tunnels.get(lastUsedName));
|
||||
}
|
||||
|
||||
public void refreshTunnelStates() {
|
||||
asyncWorker.supplyAsync(backend::enumerate)
|
||||
.thenAccept(running -> {
|
||||
for (final Tunnel tunnel : tunnels)
|
||||
tunnel.onStateChanged(running.contains(tunnel.getName()) ? State.UP : State.DOWN);
|
||||
})
|
||||
.whenComplete(ExceptionLoggers.E);
|
||||
}
|
||||
|
||||
public CompletionStage<Void> restoreState() {
|
||||
if (!preferences.getBoolean(KEY_RESTORE_ON_BOOT, false))
|
||||
return CompletableFuture.completedFuture(null);
|
||||
final Set<String> previouslyRunning = preferences.getStringSet(KEY_RUNNING_TUNNELS, null);
|
||||
if (previouslyRunning == null)
|
||||
return CompletableFuture.completedFuture(null);
|
||||
return CompletableFuture.allOf(StreamSupport.stream(tunnels)
|
||||
.filter(tunnel -> previouslyRunning.contains(tunnel.getName()))
|
||||
.map(tunnel -> setTunnelState(tunnel, State.UP))
|
||||
.toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
public void saveState() {
|
||||
final Set<String> runningTunnels = StreamSupport.stream(tunnels)
|
||||
.filter(tunnel -> tunnel.getState() == State.UP)
|
||||
.map(Tunnel::getName)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
preferences.edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply();
|
||||
}
|
||||
|
||||
private void setLastUsedTunnel(final Tunnel tunnel) {
|
||||
if (tunnel == lastUsedTunnel)
|
||||
return;
|
||||
lastUsedTunnel = tunnel;
|
||||
notifyPropertyChanged(BR.lastUsedTunnel);
|
||||
if (tunnel != null)
|
||||
preferences.edit().putString(KEY_LAST_USED_TUNNEL, tunnel.getName()).apply();
|
||||
else
|
||||
preferences.edit().remove(KEY_LAST_USED_TUNNEL).apply();
|
||||
}
|
||||
|
||||
CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) {
|
||||
return asyncWorker.supplyAsync(() -> {
|
||||
final Config appliedConfig = backend.applyConfig(tunnel, config);
|
||||
return configStore.save(tunnel.getName(), appliedConfig);
|
||||
}).thenApply(tunnel::onConfigChanged);
|
||||
}
|
||||
|
||||
CompletionStage<String> setTunnelName(final Tunnel tunnel, final String name) {
|
||||
if (Tunnel.isNameInvalid(name))
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name"));
|
||||
if (tunnels.containsKey(name)) {
|
||||
final String message = "Tunnel " + name + " already exists";
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(message));
|
||||
}
|
||||
final State originalState = tunnel.getState();
|
||||
final boolean wasLastUsed = tunnel == lastUsedTunnel;
|
||||
// Make sure nothing touches the tunnel.
|
||||
if (wasLastUsed)
|
||||
setLastUsedTunnel(null);
|
||||
tunnels.remove(tunnel);
|
||||
return asyncWorker.supplyAsync(() -> {
|
||||
if (originalState == State.UP)
|
||||
backend.setState(tunnel, State.DOWN);
|
||||
configStore.rename(tunnel.getName(), name);
|
||||
final String newName = tunnel.onNameChanged(name);
|
||||
if (originalState == State.UP)
|
||||
backend.setState(tunnel, State.UP);
|
||||
return newName;
|
||||
}).whenComplete((newName, e) -> {
|
||||
// On failure, we don't know what state the tunnel might be in. Fix that.
|
||||
if (e != null)
|
||||
getTunnelState(tunnel);
|
||||
// Add the tunnel back to the manager, under whatever name it thinks it has.
|
||||
tunnels.add(tunnel);
|
||||
if (wasLastUsed)
|
||||
setLastUsedTunnel(tunnel);
|
||||
});
|
||||
}
|
||||
|
||||
CompletionStage<State> setTunnelState(final Tunnel tunnel, final State state) {
|
||||
// Ensure the configuration is loaded before trying to use it.
|
||||
return tunnel.getConfigAsync().thenCompose(x ->
|
||||
asyncWorker.supplyAsync(() -> backend.setState(tunnel, state))
|
||||
).whenComplete((newState, e) -> {
|
||||
// Ensure onStateChanged is always called (failure or not), and with the correct state.
|
||||
tunnel.onStateChanged(e == null ? newState : tunnel.getState());
|
||||
if (e == null && newState == State.UP)
|
||||
setLastUsedTunnel(tunnel);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.system.OsConstants;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.Application.ApplicationComponent;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.util.AsyncWorker;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
|
||||
/**
|
||||
* Preference implementing a button that asynchronously runs {@code ToolsInstaller} and displays the
|
||||
* result as the preference summary.
|
||||
*/
|
||||
|
||||
public class ToolsInstallerPreference extends Preference {
|
||||
private final AsyncWorker asyncWorker;
|
||||
private final ToolsInstaller toolsInstaller;
|
||||
private State state = State.INITIAL;
|
||||
|
||||
@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
|
||||
public ToolsInstallerPreference(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
final ApplicationComponent applicationComponent = Application.getComponent();
|
||||
asyncWorker = applicationComponent.getAsyncWorker();
|
||||
toolsInstaller = applicationComponent.getToolsInstaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
return getContext().getString(state.messageResourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return getContext().getString(R.string.tools_installer_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttached() {
|
||||
super.onAttached();
|
||||
asyncWorker.supplyAsync(toolsInstaller::areInstalled).whenComplete(this::onCheckResult);
|
||||
}
|
||||
|
||||
private void onCheckResult(final Integer result, final Throwable throwable) {
|
||||
setState(throwable == null && result == OsConstants.EALREADY ?
|
||||
State.ALREADY : State.INITIAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
setState(State.WORKING);
|
||||
asyncWorker.supplyAsync(toolsInstaller::install).whenComplete(this::onInstallResult);
|
||||
}
|
||||
|
||||
private void onInstallResult(final Integer result, final Throwable throwable) {
|
||||
final State nextState;
|
||||
if (throwable != null)
|
||||
nextState = State.FAILURE;
|
||||
else if (result == OsConstants.EXIT_SUCCESS)
|
||||
nextState = State.SUCCESS;
|
||||
else if (result == OsConstants.EALREADY)
|
||||
nextState = State.ALREADY;
|
||||
else
|
||||
nextState = State.FAILURE;
|
||||
setState(nextState);
|
||||
}
|
||||
|
||||
private void setState(@NonNull final State state) {
|
||||
if (this.state == state)
|
||||
return;
|
||||
this.state = state;
|
||||
if (isEnabled() != state.shouldEnableView)
|
||||
setEnabled(state.shouldEnableView);
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
private enum State {
|
||||
ALREADY(R.string.tools_installer_already, false),
|
||||
FAILURE(R.string.tools_installer_failure, true),
|
||||
INITIAL(R.string.tools_installer_initial, true),
|
||||
SUCCESS(R.string.tools_installer_success, false),
|
||||
WORKING(R.string.tools_installer_working, false);
|
||||
|
||||
private final int messageResourceId;
|
||||
private final boolean shouldEnableView;
|
||||
|
||||
State(final int messageResourceId, final boolean shouldEnableView) {
|
||||
this.messageResourceId = messageResourceId;
|
||||
this.shouldEnableView = shouldEnableView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.preference;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.Application.ApplicationComponent;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.activity.SettingsActivity;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.AsyncWorker;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Preference implementing a button that asynchronously exports config zips.
|
||||
*/
|
||||
|
||||
public class ZipExporterPreference extends Preference {
|
||||
private static final String TAG = "WireGuard/" + ZipExporterPreference.class.getSimpleName();
|
||||
|
||||
private final AsyncWorker asyncWorker;
|
||||
private final TunnelManager tunnelManager;
|
||||
private String exportedFilePath;
|
||||
|
||||
@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
|
||||
public ZipExporterPreference(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
final ApplicationComponent applicationComponent = Application.getComponent();
|
||||
asyncWorker = applicationComponent.getAsyncWorker();
|
||||
tunnelManager = applicationComponent.getTunnelManager();
|
||||
}
|
||||
|
||||
private static SettingsActivity getPrefActivity(final Preference preference) {
|
||||
final Context context = preference.getContext();
|
||||
if (context instanceof ContextThemeWrapper) {
|
||||
if (((ContextThemeWrapper) context).getBaseContext() instanceof SettingsActivity) {
|
||||
return ((SettingsActivity) ((ContextThemeWrapper) context).getBaseContext());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void exportZip() {
|
||||
final List<Tunnel> tunnels = new ArrayList<>(tunnelManager.getTunnels());
|
||||
final List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size());
|
||||
for (final Tunnel tunnel : tunnels)
|
||||
futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture());
|
||||
if (futureConfigs.isEmpty()) {
|
||||
exportZipComplete(null, new IllegalArgumentException("No tunnels exist"));
|
||||
return;
|
||||
}
|
||||
CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()]))
|
||||
.whenComplete((ignored1, exception) -> asyncWorker.supplyAsync(() -> {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
final File file = new File(path, "wireguard-export.zip");
|
||||
if (!path.isDirectory() && !path.mkdirs())
|
||||
throw new IOException("Cannot create output directory");
|
||||
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file))) {
|
||||
for (int i = 0; i < futureConfigs.size(); ++i) {
|
||||
zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf"));
|
||||
zip.write(futureConfigs.get(i).getNow(null).
|
||||
toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
zip.closeEntry();
|
||||
zip.close();
|
||||
} catch (Exception e) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
throw e;
|
||||
}
|
||||
return file.getAbsolutePath();
|
||||
}).whenComplete(this::exportZipComplete));
|
||||
}
|
||||
|
||||
private void exportZipComplete(final String filePath, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
final String message = getContext().getString(R.string.export_error, error);
|
||||
Log.e(TAG, message, throwable);
|
||||
Snackbar.make(
|
||||
getPrefActivity(this).findViewById(android.R.id.content),
|
||||
message, Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
exportedFilePath = filePath;
|
||||
setEnabled(false);
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
return exportedFilePath == null ?
|
||||
getContext().getString(R.string.export_summary) :
|
||||
getContext().getString(R.string.export_success, exportedFilePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return getContext().getString(R.string.zip_exporter_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
getPrefActivity(this).ensurePermissions(
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
(permissions, granted) -> {
|
||||
if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED)
|
||||
exportZip();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import com.wireguard.android.Application.ApplicationHandler;
|
||||
import com.wireguard.android.Application.ApplicationScope;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletionStage;
|
||||
|
||||
/**
|
||||
* Helper class for running asynchronous tasks and ensuring they are completed on the main thread.
|
||||
*/
|
||||
|
||||
@ApplicationScope
|
||||
public class AsyncWorker {
|
||||
private final Executor executor;
|
||||
private final Handler handler;
|
||||
|
||||
@Inject
|
||||
AsyncWorker(final Executor executor, @ApplicationHandler final Handler handler) {
|
||||
this.executor = executor;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public CompletionStage<Void> runAsync(final AsyncRunnable<?> runnable) {
|
||||
final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
runnable.run();
|
||||
handler.post(() -> future.complete(null));
|
||||
} catch (final Throwable t) {
|
||||
handler.post(() -> future.completeExceptionally(t));
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
public <T> CompletionStage<T> supplyAsync(final AsyncSupplier<T, ?> supplier) {
|
||||
final CompletableFuture<T> future = new CompletableFuture<>();
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
final T result = supplier.get();
|
||||
handler.post(() -> future.complete(result));
|
||||
} catch (final Throwable t) {
|
||||
handler.post(() -> future.completeExceptionally(t));
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AsyncRunnable<E extends Throwable> {
|
||||
void run() throws E;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AsyncSupplier<T, E extends Throwable> {
|
||||
T get() throws E;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Standalone utilities for interacting with the system clipboard.
|
||||
*/
|
||||
|
||||
public final class ClipboardUtils {
|
||||
private ClipboardUtils() {
|
||||
// Prevent instantiation
|
||||
}
|
||||
|
||||
public static void copyTextView(final View view) {
|
||||
if (!(view instanceof TextView))
|
||||
return;
|
||||
final CharSequence text = ((TextView) view).getText();
|
||||
if (text == null || text.length() == 0)
|
||||
return;
|
||||
final Object service = view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (!(service instanceof ClipboardManager))
|
||||
return;
|
||||
final CharSequence description = view.getContentDescription();
|
||||
((ClipboardManager) service).setPrimaryClip(ClipData.newPlainText(description, text));
|
||||
Snackbar.make(view, description + " copied to clipboard", Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import java9.util.concurrent.CompletionException;
|
||||
import java9.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Helpers for logging exceptions from asynchronous tasks. These can be passed to
|
||||
* {@code CompletionStage.whenComplete()} at the end of an asynchronous future chain.
|
||||
*/
|
||||
|
||||
public enum ExceptionLoggers implements BiConsumer<Object, Throwable> {
|
||||
D(Log.DEBUG),
|
||||
E(Log.ERROR);
|
||||
|
||||
private static final String TAG = "WireGuard/" + ExceptionLoggers.class.getSimpleName();
|
||||
|
||||
private final int priority;
|
||||
|
||||
ExceptionLoggers(final int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public static Throwable unwrap(final Throwable throwable) {
|
||||
if (throwable instanceof CompletionException && throwable.getCause() != null)
|
||||
return throwable.getCause();
|
||||
return throwable;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String unwrapMessage(Throwable throwable) {
|
||||
throwable = unwrap(throwable);
|
||||
final String message = throwable.getMessage();
|
||||
if (message != null)
|
||||
return message;
|
||||
return throwable.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(final Object result, final Throwable throwable) {
|
||||
if (throwable != null)
|
||||
Log.println(Log.ERROR, TAG, Log.getStackTraceString(throwable));
|
||||
else if (priority <= Log.DEBUG)
|
||||
Log.println(priority, TAG, "Future completed successfully");
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
/**
|
||||
* Interface for objects that have a identifying key of the given type.
|
||||
*/
|
||||
|
||||
public interface Keyed<K> {
|
||||
K getKey();
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A list containing elements that can be looked up by key. A {@code KeyedList} cannot contain
|
||||
* {@code null} elements.
|
||||
*/
|
||||
|
||||
public interface KeyedList<K, E extends Keyed<? extends K>> extends List<E> {
|
||||
boolean containsAllKeys(Collection<K> keys);
|
||||
|
||||
boolean containsKey(K key);
|
||||
|
||||
E get(K key);
|
||||
|
||||
E getLast(K key);
|
||||
|
||||
int indexOfKey(K key);
|
||||
|
||||
int lastIndexOfKey(K key);
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.databinding.ObservableArrayList;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ArrayList that allows looking up elements by some key property. As the key property must always
|
||||
* be retrievable, this list cannot hold {@code null} elements. Because this class places no
|
||||
* restrictions on the order or duplication of keys, lookup by key, as well as all list modification
|
||||
* operations, require O(n) time.
|
||||
*/
|
||||
|
||||
public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>>
|
||||
extends ObservableArrayList<E> implements ObservableKeyedList<K, E> {
|
||||
@Override
|
||||
public boolean add(final E e) {
|
||||
if (e == null)
|
||||
throw new NullPointerException("Trying to add a null element");
|
||||
return super.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(final int index, final E e) {
|
||||
if (e == null)
|
||||
throw new NullPointerException("Trying to add a null element");
|
||||
super.add(index, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NonNull final Collection<? extends E> c) {
|
||||
if (c.contains(null))
|
||||
throw new NullPointerException("Trying to add a collection with null element(s)");
|
||||
return super.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(final int index, @NonNull final Collection<? extends E> c) {
|
||||
if (c.contains(null))
|
||||
throw new NullPointerException("Trying to add a collection with null element(s)");
|
||||
return super.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAllKeys(final Collection<K> keys) {
|
||||
for (final K key : keys)
|
||||
if (!containsKey(key))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(final K key) {
|
||||
return indexOfKey(key) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(final K key) {
|
||||
final int index = indexOfKey(key);
|
||||
return index >= 0 ? get(index) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getLast(final K key) {
|
||||
final int index = lastIndexOfKey(key);
|
||||
return index >= 0 ? get(index) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOfKey(final K key) {
|
||||
final ListIterator<E> iterator = listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
final int index = iterator.nextIndex();
|
||||
if (Objects.equals(iterator.next().getKey(), key))
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOfKey(final K key) {
|
||||
final ListIterator<E> iterator = listIterator(size());
|
||||
while (iterator.hasPrevious()) {
|
||||
final int index = iterator.previousIndex();
|
||||
if (Objects.equals(iterator.previous().getKey(), key))
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(final int index, final E e) {
|
||||
if (e == null)
|
||||
throw new NullPointerException("Trying to set a null key");
|
||||
return super.set(index, e);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.databinding.ObservableList;
|
||||
|
||||
/**
|
||||
* A list that is both keyed and observable.
|
||||
*/
|
||||
|
||||
public interface ObservableKeyedList<K, E extends Keyed<? extends K>>
|
||||
extends KeyedList<K, E>, ObservableList<E> {
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
|
||||
/**
|
||||
* KeyedArrayList that enforces uniqueness and sorted order across the set of keys. This class uses
|
||||
* binary search to improve lookup and replacement times to O(log(n)). However, due to the
|
||||
* array-based nature of this class, insertion and removal of elements with anything but the largest
|
||||
* key still require O(n) time.
|
||||
*/
|
||||
|
||||
public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
|
||||
extends ObservableKeyedArrayList<K, E> implements ObservableSortedKeyedList<K, E> {
|
||||
private final Comparator<? super K> comparator;
|
||||
private final transient KeyList<K, E> keyList = new KeyList<>(this);
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public ObservableSortedKeyedArrayList() {
|
||||
comparator = null;
|
||||
}
|
||||
|
||||
public ObservableSortedKeyedArrayList(final Comparator<? super K> comparator) {
|
||||
this.comparator = comparator;
|
||||
}
|
||||
|
||||
public ObservableSortedKeyedArrayList(final Collection<? extends E> c) {
|
||||
this();
|
||||
addAll(c);
|
||||
}
|
||||
|
||||
public ObservableSortedKeyedArrayList(final SortedKeyedList<K, E> other) {
|
||||
this(other.comparator());
|
||||
addAll(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(final E e) {
|
||||
final int insertionPoint = getInsertionPoint(e);
|
||||
if (insertionPoint < 0) {
|
||||
// Skipping insertion is non-destructive if the new and existing objects are the same.
|
||||
if (e == get(-insertionPoint - 1))
|
||||
return false;
|
||||
throw new IllegalArgumentException("Element with same key already exists in list");
|
||||
}
|
||||
super.add(insertionPoint, e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(final int index, final E e) {
|
||||
final int insertionPoint = getInsertionPoint(e);
|
||||
if (insertionPoint < 0)
|
||||
throw new IllegalArgumentException("Element with same key already exists in list");
|
||||
if (insertionPoint != index)
|
||||
throw new IndexOutOfBoundsException("Wrong index given for element");
|
||||
super.add(index, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NonNull final Collection<? extends E> c) {
|
||||
boolean didChange = false;
|
||||
for (final E e : c)
|
||||
if (add(e))
|
||||
didChange = true;
|
||||
return didChange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, @NonNull final Collection<? extends E> c) {
|
||||
for (final E e : c)
|
||||
add(index++, e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<? super K> comparator() {
|
||||
return comparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K firstKey() {
|
||||
if (isEmpty())
|
||||
// The parameter in the exception is only to shut
|
||||
// lint up, we never care for the exception message.
|
||||
throw new NoSuchElementException("Empty set");
|
||||
return get(0).getKey();
|
||||
}
|
||||
|
||||
private int getInsertionPoint(final E e) {
|
||||
if (comparator != null) {
|
||||
return -Collections.binarySearch(keyList, e.getKey(), comparator) - 1;
|
||||
} else {
|
||||
@SuppressWarnings("unchecked") final List<Comparable<? super K>> list =
|
||||
(List<Comparable<? super K>>) keyList;
|
||||
return -Collections.binarySearch(list, e.getKey()) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOfKey(final K key) {
|
||||
final int index;
|
||||
if (comparator != null) {
|
||||
index = Collections.binarySearch(keyList, key, comparator);
|
||||
} else {
|
||||
@SuppressWarnings("unchecked") final List<Comparable<? super K>> list =
|
||||
(List<Comparable<? super K>>) keyList;
|
||||
index = Collections.binarySearch(list, key);
|
||||
}
|
||||
return index >= 0 ? index : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Set<K> keySet() {
|
||||
return keyList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOfKey(final K key) {
|
||||
// There can never be more than one element with the same key in the list.
|
||||
return indexOfKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public K lastKey() {
|
||||
if (isEmpty())
|
||||
// The parameter in the exception is only to shut
|
||||
// lint up, we never care for the exception message.
|
||||
throw new NoSuchElementException("Empty set");
|
||||
return get(size() - 1).getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(final int index, final E e) {
|
||||
final int order;
|
||||
if (comparator != null) {
|
||||
order = comparator.compare(e.getKey(), get(index).getKey());
|
||||
} else {
|
||||
@SuppressWarnings("unchecked") final Comparable<? super K> key =
|
||||
(Comparable<? super K>) e.getKey();
|
||||
order = key.compareTo(get(index).getKey());
|
||||
}
|
||||
if (order != 0) {
|
||||
// Allow replacement if the new key would be inserted adjacent to the replaced element.
|
||||
final int insertionPoint = getInsertionPoint(e);
|
||||
if (insertionPoint < index || insertionPoint > index + 1)
|
||||
throw new IndexOutOfBoundsException("Wrong index given for element");
|
||||
}
|
||||
return super.set(index, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Collection<E> values() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final class KeyList<K, E extends Keyed<? extends K>>
|
||||
extends AbstractList<K> implements Set<K> {
|
||||
private final ObservableSortedKeyedArrayList<K, E> list;
|
||||
|
||||
private KeyList(final ObservableSortedKeyedArrayList<K, E> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K get(final int index) {
|
||||
return list.get(index).getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
public Spliterator<K> spliterator() {
|
||||
return super.spliterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
/**
|
||||
* A list that is both sorted/keyed and observable.
|
||||
*/
|
||||
|
||||
public interface ObservableSortedKeyedList<K, E extends Keyed<? extends K>>
|
||||
extends ObservableKeyedList<K, E>, SortedKeyedList<K, E> {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A keyed list where all elements are sorted by the comparator returned by {@code comparator()}
|
||||
* applied to their keys.
|
||||
*/
|
||||
|
||||
public interface SortedKeyedList<K, E extends Keyed<? extends K>> extends KeyedList<K, E> {
|
||||
Comparator<? super K> comparator();
|
||||
|
||||
K firstKey();
|
||||
|
||||
Set<K> keySet();
|
||||
|
||||
K lastKey();
|
||||
|
||||
Collection<E> values();
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.Application.ApplicationContext;
|
||||
import com.wireguard.android.Application.ApplicationScope;
|
||||
import com.wireguard.android.util.RootShell.NoRootException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Helper to install WireGuard tools to the system partition.
|
||||
*/
|
||||
|
||||
@ApplicationScope
|
||||
public final class ToolsInstaller {
|
||||
private static final String[][] EXECUTABLES = {
|
||||
{"libwg.so", "wg"},
|
||||
{"libwg-quick.so", "wg-quick"},
|
||||
};
|
||||
private static final File[] INSTALL_DIRS = {
|
||||
new File("/system/xbin"),
|
||||
new File("/system/bin"),
|
||||
};
|
||||
private static final File INSTALL_DIR = getInstallDir();
|
||||
private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName();
|
||||
|
||||
private final File localBinaryDir;
|
||||
private final Object lock = new Object();
|
||||
private final File nativeLibraryDir;
|
||||
private final RootShell rootShell;
|
||||
private Boolean areToolsAvailable;
|
||||
|
||||
@Inject
|
||||
public ToolsInstaller(@ApplicationContext final Context context, final RootShell rootShell) {
|
||||
localBinaryDir = new File(context.getCacheDir(), "bin");
|
||||
nativeLibraryDir = new File(context.getApplicationInfo().nativeLibraryDir);
|
||||
this.rootShell = rootShell;
|
||||
}
|
||||
|
||||
private static File getInstallDir() {
|
||||
final String path = System.getenv("PATH");
|
||||
if (path == null)
|
||||
return INSTALL_DIRS[0];
|
||||
final List<String> paths = Arrays.asList(path.split(":"));
|
||||
for (final File dir : INSTALL_DIRS)
|
||||
if (paths.contains(dir.getPath()) && dir.isDirectory())
|
||||
return dir;
|
||||
return null;
|
||||
}
|
||||
|
||||
public int areInstalled() throws NoRootException {
|
||||
if (INSTALL_DIR == null)
|
||||
return OsConstants.ENOENT;
|
||||
final StringBuilder script = new StringBuilder();
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
script.append(String.format("cmp -s '%s' '%s' && ",
|
||||
new File(nativeLibraryDir, names[0]),
|
||||
new File(INSTALL_DIR, names[1])));
|
||||
}
|
||||
script.append("exit ").append(OsConstants.EALREADY).append(';');
|
||||
try {
|
||||
return rootShell.run(null, script.toString());
|
||||
} catch (final IOException ignored) {
|
||||
return OsConstants.EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureToolsAvailable() throws FileNotFoundException, NoRootException {
|
||||
synchronized (lock) {
|
||||
if (areToolsAvailable == null) {
|
||||
final int ret = symlink();
|
||||
if (ret == OsConstants.EALREADY) {
|
||||
Log.d(TAG, "Tools were already symlinked into our private binary dir");
|
||||
areToolsAvailable = true;
|
||||
} else if (ret == OsConstants.EXIT_SUCCESS) {
|
||||
Log.d(TAG, "Tools are now symlinked into our private binary dir");
|
||||
areToolsAvailable = true;
|
||||
} else {
|
||||
Log.e(TAG, "For some reason, wg and wg-quick are not available at all");
|
||||
areToolsAvailable = false;
|
||||
}
|
||||
}
|
||||
if (!areToolsAvailable)
|
||||
throw new FileNotFoundException("Required tools unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
public int install() throws NoRootException {
|
||||
if (INSTALL_DIR == null)
|
||||
return OsConstants.ENOENT;
|
||||
final StringBuilder script = new StringBuilder("set -ex; ");
|
||||
script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; ");
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
final File destination = new File(INSTALL_DIR, names[1]);
|
||||
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ",
|
||||
new File(nativeLibraryDir, names[0]), destination, destination, destination));
|
||||
}
|
||||
try {
|
||||
return rootShell.run(null, script.toString());
|
||||
} catch (final IOException ignored) {
|
||||
return OsConstants.EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
public int symlink() throws NoRootException {
|
||||
final StringBuilder script = new StringBuilder("set -x; ");
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
script.append(String.format("test '%s' -ef '%s' && ",
|
||||
new File(nativeLibraryDir, names[0]),
|
||||
new File(localBinaryDir, names[1])));
|
||||
}
|
||||
script.append("exit ").append(OsConstants.EALREADY).append("; set -e; ");
|
||||
|
||||
for (final String[] names : EXECUTABLES) {
|
||||
script.append(String.format("ln -fns '%s' '%s'; ",
|
||||
new File(nativeLibraryDir, names[0]),
|
||||
new File(localBinaryDir, names[1])));
|
||||
}
|
||||
script.append("exit ").append(OsConstants.EXIT_SUCCESS).append(';');
|
||||
|
||||
try {
|
||||
return rootShell.run(null, script.toString());
|
||||
} catch (final IOException ignored) {
|
||||
return OsConstants.EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.widget;
|
||||
|
||||
import android.text.InputFilter;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
|
||||
import com.wireguard.crypto.KeyEncoding;
|
||||
|
||||
/**
|
||||
* InputFilter for entering WireGuard private/public keys encoded with base64.
|
||||
*/
|
||||
|
||||
public class KeyInputFilter implements InputFilter {
|
||||
private static boolean isAllowed(final char c) {
|
||||
return Character.isLetterOrDigit(c) || c == '+' || c == '/';
|
||||
}
|
||||
|
||||
public static InputFilter newInstance() {
|
||||
return new KeyInputFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence filter(final CharSequence source,
|
||||
final int sStart, final int sEnd,
|
||||
final Spanned dest,
|
||||
final int dStart, final int dEnd) {
|
||||
SpannableStringBuilder replacement = null;
|
||||
int rIndex = 0;
|
||||
final int dLength = dest.length();
|
||||
for (int sIndex = sStart; sIndex < sEnd; ++sIndex) {
|
||||
final char c = source.charAt(sIndex);
|
||||
final int dIndex = dStart + (sIndex - sStart);
|
||||
// Restrict characters to the base64 character set.
|
||||
// Ensure adding this character does not push the length over the limit.
|
||||
if (((dIndex + 1 < KeyEncoding.KEY_LENGTH_BASE64 && isAllowed(c)) ||
|
||||
(dIndex + 1 == KeyEncoding.KEY_LENGTH_BASE64 && c == '=')) &&
|
||||
dLength + (sIndex - sStart) < KeyEncoding.KEY_LENGTH_BASE64) {
|
||||
++rIndex;
|
||||
} else {
|
||||
if (replacement == null)
|
||||
replacement = new SpannableStringBuilder(source, sStart, sEnd);
|
||||
replacement.delete(rIndex, rIndex + 1);
|
||||
}
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.android.widget;
|
||||
|
||||
import android.text.InputFilter;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
|
||||
/**
|
||||
* InputFilter for entering WireGuard configuration names (Linux interface names).
|
||||
*/
|
||||
|
||||
public class NameInputFilter implements InputFilter {
|
||||
private static boolean isAllowed(final char c) {
|
||||
return Character.isLetterOrDigit(c) || "_=+.-".indexOf(c) >= 0;
|
||||
}
|
||||
|
||||
public static InputFilter newInstance() {
|
||||
return new NameInputFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence filter(final CharSequence source,
|
||||
final int sStart, final int sEnd,
|
||||
final Spanned dest,
|
||||
final int dStart, final int dEnd) {
|
||||
SpannableStringBuilder replacement = null;
|
||||
int rIndex = 0;
|
||||
final int dLength = dest.length();
|
||||
for (int sIndex = sStart; sIndex < sEnd; ++sIndex) {
|
||||
final char c = source.charAt(sIndex);
|
||||
final int dIndex = dStart + (sIndex - sStart);
|
||||
// Restrict characters to those valid in interfaces.
|
||||
// Ensure adding this character does not push the length over the limit.
|
||||
if ((dIndex < Tunnel.NAME_MAX_LENGTH && isAllowed(c)) &&
|
||||
dLength + (sIndex - sStart) < Tunnel.NAME_MAX_LENGTH) {
|
||||
++rIndex;
|
||||
} else {
|
||||
if (replacement == null)
|
||||
replacement = new SpannableStringBuilder(source, sStart, sEnd);
|
||||
replacement.delete(rIndex, rIndex + 1);
|
||||
}
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 The Android Open Source Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Switch;
|
||||
|
||||
public class ToggleSwitch extends Switch {
|
||||
private boolean isRestoringState;
|
||||
private OnBeforeCheckedChangeListener listener;
|
||||
|
||||
@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
|
||||
public ToggleSwitch(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ToggleSwitch(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(final Parcelable state) {
|
||||
isRestoringState = true;
|
||||
super.onRestoreInstanceState(state);
|
||||
isRestoringState = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(final boolean checked) {
|
||||
if (checked == isChecked())
|
||||
return;
|
||||
if (isRestoringState || listener == null) {
|
||||
super.setChecked(checked);
|
||||
return;
|
||||
}
|
||||
setEnabled(false);
|
||||
listener.onBeforeCheckedChanged(this, checked);
|
||||
}
|
||||
|
||||
public void setCheckedInternal(final boolean checked) {
|
||||
super.setChecked(checked);
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
public void setOnBeforeCheckedChangeListener(final OnBeforeCheckedChangeListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public interface OnBeforeCheckedChangeListener {
|
||||
void onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* The set of valid attributes for an interface or peer in a WireGuard configuration file.
|
||||
*/
|
||||
|
||||
enum Attribute {
|
||||
ADDRESS("Address"),
|
||||
ALLOWED_IPS("AllowedIPs"),
|
||||
DNS("DNS"),
|
||||
ENDPOINT("Endpoint"),
|
||||
LISTEN_PORT("ListenPort"),
|
||||
MTU("MTU"),
|
||||
PERSISTENT_KEEPALIVE("PersistentKeepalive"),
|
||||
PRESHARED_KEY("PresharedKey"),
|
||||
PRIVATE_KEY("PrivateKey"),
|
||||
PUBLIC_KEY("PublicKey");
|
||||
|
||||
private static final String[] EMPTY_LIST = new String[0];
|
||||
private static final Map<String, Attribute> KEY_MAP;
|
||||
private static final Pattern LIST_SEPARATOR_PATTERN = Pattern.compile("\\s*,\\s*");
|
||||
private static final Method NUMERIC_ADDRESS_PARSER;
|
||||
private static final Pattern SEPARATOR_PATTERN = Pattern.compile("\\s|=");
|
||||
|
||||
static {
|
||||
KEY_MAP = new HashMap<>(Attribute.values().length);
|
||||
for (final Attribute key : Attribute.values()) {
|
||||
KEY_MAP.put(key.token.toLowerCase(), key);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
NUMERIC_ADDRESS_PARSER = InetAddress.class.getMethod("parseNumericAddress", String.class);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final Pattern pattern;
|
||||
private final String token;
|
||||
|
||||
Attribute(final String token) {
|
||||
pattern = Pattern.compile(token + "\\s*=\\s*(\\S.*)");
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public static <T> String iterableToString(final Iterable<T> iterable) {
|
||||
return TextUtils.join(", ", iterable);
|
||||
}
|
||||
|
||||
public static Attribute match(final CharSequence line) {
|
||||
return KEY_MAP.get(SEPARATOR_PATTERN.split(line)[0].toLowerCase());
|
||||
}
|
||||
|
||||
public static InetAddress parseIPString(final String address) {
|
||||
if (address == null || address.isEmpty())
|
||||
throw new IllegalArgumentException("Empty address");
|
||||
try {
|
||||
return (InetAddress) NUMERIC_ADDRESS_PARSER.invoke(null, address);
|
||||
} catch (final IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (final InvocationTargetException e) {
|
||||
if (e.getCause() instanceof IllegalArgumentException)
|
||||
throw (IllegalArgumentException) e.getCause();
|
||||
else
|
||||
throw new IllegalArgumentException(e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] stringToList(final String string) {
|
||||
if (string == null)
|
||||
return EMPTY_LIST;
|
||||
return LIST_SEPARATOR_PATTERN.split(string.trim());
|
||||
}
|
||||
|
||||
public String composeWith(final Object value) {
|
||||
return String.format("%s = %s%n", token, value);
|
||||
}
|
||||
|
||||
public String composeWith(final int value) {
|
||||
return String.format(Locale.getDefault(), "%s = %d%n", token, value);
|
||||
}
|
||||
|
||||
public <T> String composeWith(final Iterable<T> value) {
|
||||
return String.format("%s = %s%n", token, iterableToString(value));
|
||||
}
|
||||
|
||||
public String parse(final CharSequence line) {
|
||||
final Matcher matcher = pattern.matcher(line);
|
||||
return matcher.matches() ? matcher.group(1) : null;
|
||||
}
|
||||
|
||||
public String[] parseList(final CharSequence line) {
|
||||
final Matcher matcher = pattern.matcher(line);
|
||||
return matcher.matches() ? stringToList(matcher.group(1)) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import android.databinding.BaseObservable;
|
||||
import android.databinding.Bindable;
|
||||
import android.databinding.ObservableArrayList;
|
||||
import android.databinding.ObservableList;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.databinding.library.baseAdapters.BR;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a wg-quick configuration file, its name, and its connection state.
|
||||
*/
|
||||
|
||||
public class Config {
|
||||
private final Interface interfaceSection = new Interface();
|
||||
private List<Peer> peers = new ArrayList<>();
|
||||
|
||||
public static Config from(final InputStream stream) throws IOException {
|
||||
return from(new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
public static Config from(final BufferedReader reader) throws IOException {
|
||||
final Config config = new Config();
|
||||
Peer currentPeer = null;
|
||||
String line;
|
||||
boolean inInterfaceSection = false;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
final int commentIndex = line.indexOf('#');
|
||||
if (commentIndex != -1)
|
||||
line = line.substring(0, commentIndex);
|
||||
line = line.trim();
|
||||
if (line.isEmpty())
|
||||
continue;
|
||||
if ("[Interface]".toLowerCase().equals(line.toLowerCase())) {
|
||||
currentPeer = null;
|
||||
inInterfaceSection = true;
|
||||
} else if ("[Peer]".toLowerCase().equals(line.toLowerCase())) {
|
||||
currentPeer = new Peer();
|
||||
config.peers.add(currentPeer);
|
||||
inInterfaceSection = false;
|
||||
} else if (inInterfaceSection) {
|
||||
config.interfaceSection.parse(line);
|
||||
} else if (currentPeer != null) {
|
||||
currentPeer.parse(line);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid configuration line: " + line);
|
||||
}
|
||||
}
|
||||
if (!inInterfaceSection && currentPeer == null) {
|
||||
throw new IllegalArgumentException("Could not find any config information");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public Interface getInterface() {
|
||||
return interfaceSection;
|
||||
}
|
||||
|
||||
public List<Peer> getPeers() {
|
||||
return peers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder().append(interfaceSection);
|
||||
for (final Peer peer : peers)
|
||||
sb.append('\n').append(peer);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static class Observable extends BaseObservable implements Parcelable {
|
||||
public static final Creator<Observable> CREATOR = new Creator<Observable>() {
|
||||
@Override
|
||||
public Observable createFromParcel(final Parcel in) {
|
||||
return new Observable(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable[] newArray(final int size) {
|
||||
return new Observable[size];
|
||||
}
|
||||
};
|
||||
private String name;
|
||||
private Interface.Observable observableInterface;
|
||||
private ObservableList<Peer.Observable> observablePeers;
|
||||
|
||||
public Observable(final Config parent, final String name) {
|
||||
this.name = name;
|
||||
loadData(parent);
|
||||
}
|
||||
|
||||
private Observable(final Parcel in) {
|
||||
name = in.readString();
|
||||
observableInterface = in.readParcelable(Interface.Observable.class.getClassLoader());
|
||||
observablePeers = new ObservableArrayList<>();
|
||||
in.readTypedList(observablePeers, Peer.Observable.CREATOR);
|
||||
}
|
||||
|
||||
public void commitData(final Config parent) {
|
||||
observableInterface.commitData(parent.interfaceSection);
|
||||
final List<Peer> newPeers = new ArrayList<>(observablePeers.size());
|
||||
for (final Peer.Observable observablePeer : observablePeers) {
|
||||
final Peer peer = new Peer();
|
||||
observablePeer.commitData(peer);
|
||||
newPeers.add(peer);
|
||||
}
|
||||
parent.peers = newPeers;
|
||||
notifyChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public Interface.Observable getInterfaceSection() {
|
||||
return observableInterface;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getName() {
|
||||
return name == null ? "" : name;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public ObservableList<Peer.Observable> getPeers() {
|
||||
return observablePeers;
|
||||
}
|
||||
|
||||
protected void loadData(final Config parent) {
|
||||
observableInterface = new Interface.Observable(parent == null ? null : parent.interfaceSection);
|
||||
observablePeers = new ObservableArrayList<>();
|
||||
if (parent != null) {
|
||||
for (final Peer peer : parent.getPeers())
|
||||
observablePeers.add(new Peer.Observable(peer));
|
||||
}
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
notifyPropertyChanged(BR.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeString(name);
|
||||
dest.writeParcelable(observableInterface, flags);
|
||||
dest.writeTypedList(observablePeers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Locale;
|
||||
|
||||
public class IPCidr {
|
||||
private final InetAddress address;
|
||||
private int cidr;
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public IPCidr(String in) {
|
||||
cidr = -1;
|
||||
final int slash = in.lastIndexOf('/');
|
||||
if (slash != -1 && slash < in.length() - 1) {
|
||||
try {
|
||||
cidr = Integer.parseInt(in.substring(slash + 1), 10);
|
||||
in = in.substring(0, slash);
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
}
|
||||
address = Attribute.parseIPString(in);
|
||||
if ((address instanceof Inet6Address) && (cidr > 128 || cidr < 0))
|
||||
cidr = 128;
|
||||
else if ((address instanceof Inet4Address) && (cidr > 32 || cidr < 0))
|
||||
cidr = 32;
|
||||
}
|
||||
|
||||
public InetAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getCidr() {
|
||||
return cidr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.getDefault(), "%s/%d", address.getHostAddress(), cidr);
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import android.databinding.BaseObservable;
|
||||
import android.databinding.Bindable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.wireguard.android.BR;
|
||||
import com.wireguard.crypto.Keypair;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the configuration for a WireGuard interface (an [Interface] block).
|
||||
*/
|
||||
|
||||
public class Interface {
|
||||
private final List<IPCidr> addressList;
|
||||
private final List<InetAddress> dnsList;
|
||||
private Keypair keypair;
|
||||
private int listenPort;
|
||||
private int mtu;
|
||||
|
||||
public Interface() {
|
||||
addressList = new ArrayList<>();
|
||||
dnsList = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void addAddresses(final String[] addresses) {
|
||||
if (addresses != null && addresses.length > 0) {
|
||||
for (final String addr : addresses) {
|
||||
if (addr.isEmpty())
|
||||
throw new IllegalArgumentException("Address is empty");
|
||||
addressList.add(new IPCidr(addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDnses(final String[] dnses) {
|
||||
if (dnses != null && dnses.length > 0) {
|
||||
for (final String dns : dnses) {
|
||||
dnsList.add(Attribute.parseIPString(dns));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getAddressString() {
|
||||
if (addressList.isEmpty())
|
||||
return null;
|
||||
return Attribute.iterableToString(addressList);
|
||||
}
|
||||
|
||||
public IPCidr[] getAddresses() {
|
||||
return addressList.toArray(new IPCidr[addressList.size()]);
|
||||
}
|
||||
|
||||
private String getDnsString() {
|
||||
if (dnsList.isEmpty())
|
||||
return null;
|
||||
return Attribute.iterableToString(getDnsStrings());
|
||||
}
|
||||
|
||||
private List<String> getDnsStrings() {
|
||||
final List<String> strings = new ArrayList<>();
|
||||
for (final InetAddress addr : dnsList)
|
||||
strings.add(addr.getHostAddress());
|
||||
return strings;
|
||||
}
|
||||
|
||||
public InetAddress[] getDnses() {
|
||||
return dnsList.toArray(new InetAddress[dnsList.size()]);
|
||||
}
|
||||
|
||||
public int getListenPort() {
|
||||
return listenPort;
|
||||
}
|
||||
|
||||
private String getListenPortString() {
|
||||
if (listenPort == 0)
|
||||
return null;
|
||||
return Integer.valueOf(listenPort).toString();
|
||||
}
|
||||
|
||||
public int getMtu() {
|
||||
return mtu;
|
||||
}
|
||||
|
||||
private String getMtuString() {
|
||||
if (mtu == 0)
|
||||
return null;
|
||||
return Integer.toString(mtu);
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
if (keypair == null)
|
||||
return null;
|
||||
return keypair.getPrivateKey();
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
if (keypair == null)
|
||||
return null;
|
||||
return keypair.getPublicKey();
|
||||
}
|
||||
|
||||
public void parse(final String line) {
|
||||
final Attribute key = Attribute.match(line);
|
||||
switch (key) {
|
||||
case ADDRESS:
|
||||
addAddresses(key.parseList(line));
|
||||
break;
|
||||
case DNS:
|
||||
addDnses(key.parseList(line));
|
||||
break;
|
||||
case LISTEN_PORT:
|
||||
setListenPortString(key.parse(line));
|
||||
break;
|
||||
case MTU:
|
||||
setMtuString(key.parse(line));
|
||||
break;
|
||||
case PRIVATE_KEY:
|
||||
setPrivateKey(key.parse(line));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAddressString(final String addressString) {
|
||||
addressList.clear();
|
||||
addAddresses(Attribute.stringToList(addressString));
|
||||
}
|
||||
|
||||
private void setDnsString(final String dnsString) {
|
||||
dnsList.clear();
|
||||
addDnses(Attribute.stringToList(dnsString));
|
||||
}
|
||||
|
||||
private void setListenPort(final int listenPort) {
|
||||
this.listenPort = listenPort;
|
||||
}
|
||||
|
||||
private void setListenPortString(final String port) {
|
||||
if (port != null && !port.isEmpty())
|
||||
setListenPort(Integer.parseInt(port, 10));
|
||||
else
|
||||
setListenPort(0);
|
||||
}
|
||||
|
||||
private void setMtu(final int mtu) {
|
||||
this.mtu = mtu;
|
||||
}
|
||||
|
||||
private void setMtuString(final String mtu) {
|
||||
if (mtu != null && !mtu.isEmpty())
|
||||
setMtu(Integer.parseInt(mtu, 10));
|
||||
else
|
||||
setMtu(0);
|
||||
}
|
||||
|
||||
private void setPrivateKey(String privateKey) {
|
||||
if (privateKey != null && privateKey.isEmpty())
|
||||
privateKey = null;
|
||||
keypair = privateKey == null ? null : new Keypair(privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder().append("[Interface]\n");
|
||||
if (!addressList.isEmpty())
|
||||
sb.append(Attribute.ADDRESS.composeWith(addressList));
|
||||
if (!dnsList.isEmpty())
|
||||
sb.append(Attribute.DNS.composeWith(getDnsStrings()));
|
||||
if (listenPort != 0)
|
||||
sb.append(Attribute.LISTEN_PORT.composeWith(listenPort));
|
||||
if (mtu != 0)
|
||||
sb.append(Attribute.MTU.composeWith(mtu));
|
||||
if (keypair != null)
|
||||
sb.append(Attribute.PRIVATE_KEY.composeWith(keypair.getPrivateKey()));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static class Observable extends BaseObservable implements Parcelable {
|
||||
public static final Creator<Observable> CREATOR = new Creator<Observable>() {
|
||||
@Override
|
||||
public Observable createFromParcel(final Parcel in) {
|
||||
return new Observable(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable[] newArray(final int size) {
|
||||
return new Observable[size];
|
||||
}
|
||||
};
|
||||
private String addresses;
|
||||
private String dnses;
|
||||
private String listenPort;
|
||||
private String mtu;
|
||||
private String privateKey;
|
||||
private String publicKey;
|
||||
|
||||
public Observable(final Interface parent) {
|
||||
if (parent != null)
|
||||
loadData(parent);
|
||||
}
|
||||
|
||||
private Observable(final Parcel in) {
|
||||
addresses = in.readString();
|
||||
dnses = in.readString();
|
||||
publicKey = in.readString();
|
||||
privateKey = in.readString();
|
||||
listenPort = in.readString();
|
||||
mtu = in.readString();
|
||||
}
|
||||
|
||||
public void commitData(final Interface parent) {
|
||||
parent.setAddressString(addresses);
|
||||
parent.setDnsString(dnses);
|
||||
parent.setPrivateKey(privateKey);
|
||||
parent.setListenPortString(listenPort);
|
||||
parent.setMtuString(mtu);
|
||||
loadData(parent);
|
||||
notifyChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void generateKeypair() {
|
||||
final Keypair keypair = new Keypair();
|
||||
privateKey = keypair.getPrivateKey();
|
||||
publicKey = keypair.getPublicKey();
|
||||
notifyPropertyChanged(BR.privateKey);
|
||||
notifyPropertyChanged(BR.publicKey);
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getDnses() {
|
||||
return dnses;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getListenPort() {
|
||||
return listenPort;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getMtu() {
|
||||
return mtu;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
protected void loadData(final Interface parent) {
|
||||
addresses = parent.getAddressString();
|
||||
dnses = parent.getDnsString();
|
||||
publicKey = parent.getPublicKey();
|
||||
privateKey = parent.getPrivateKey();
|
||||
listenPort = parent.getListenPortString();
|
||||
mtu = parent.getMtuString();
|
||||
}
|
||||
|
||||
public void setAddresses(final String addresses) {
|
||||
this.addresses = addresses;
|
||||
notifyPropertyChanged(BR.addresses);
|
||||
}
|
||||
|
||||
public void setDnses(final String dnses) {
|
||||
this.dnses = dnses;
|
||||
notifyPropertyChanged(BR.dnses);
|
||||
}
|
||||
|
||||
public void setListenPort(final String listenPort) {
|
||||
this.listenPort = listenPort;
|
||||
notifyPropertyChanged(BR.listenPort);
|
||||
}
|
||||
|
||||
public void setMtu(final String mtu) {
|
||||
this.mtu = mtu;
|
||||
notifyPropertyChanged(BR.mtu);
|
||||
}
|
||||
|
||||
public void setPrivateKey(final String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
|
||||
try {
|
||||
publicKey = new Keypair(privateKey).getPublicKey();
|
||||
} catch (final IllegalArgumentException ignored) {
|
||||
publicKey = "";
|
||||
}
|
||||
|
||||
notifyPropertyChanged(BR.privateKey);
|
||||
notifyPropertyChanged(BR.publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeString(addresses);
|
||||
dest.writeString(dnses);
|
||||
dest.writeString(publicKey);
|
||||
dest.writeString(privateKey);
|
||||
dest.writeString(listenPort);
|
||||
dest.writeString(mtu);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import android.databinding.BaseObservable;
|
||||
import android.databinding.Bindable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.databinding.library.baseAdapters.BR;
|
||||
import com.wireguard.crypto.KeyEncoding;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Represents the configuration for a WireGuard peer (a [Peer] block).
|
||||
*/
|
||||
|
||||
public class Peer {
|
||||
private final List<IPCidr> allowedIPsList;
|
||||
private InetSocketAddress endpoint;
|
||||
private int persistentKeepalive;
|
||||
private String preSharedKey;
|
||||
private String publicKey;
|
||||
|
||||
public Peer() {
|
||||
allowedIPsList = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void addAllowedIPs(final String[] allowedIPs) {
|
||||
if (allowedIPs != null && allowedIPs.length > 0) {
|
||||
for (final String allowedIP : allowedIPs) {
|
||||
allowedIPsList.add(new IPCidr(allowedIP));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IPCidr[] getAllowedIPs() {
|
||||
return allowedIPsList.toArray(new IPCidr[allowedIPsList.size()]);
|
||||
}
|
||||
|
||||
private String getAllowedIPsString() {
|
||||
if (allowedIPsList.isEmpty())
|
||||
return null;
|
||||
return Attribute.iterableToString(allowedIPsList);
|
||||
}
|
||||
|
||||
public InetSocketAddress getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private String getEndpointString() {
|
||||
if (endpoint == null)
|
||||
return null;
|
||||
return String.format(Locale.getDefault(), "%s:%d", endpoint.getHostString(), endpoint.getPort());
|
||||
}
|
||||
|
||||
public int getPersistentKeepalive() {
|
||||
return persistentKeepalive;
|
||||
}
|
||||
|
||||
private String getPersistentKeepaliveString() {
|
||||
if (persistentKeepalive == 0)
|
||||
return null;
|
||||
return Integer.valueOf(persistentKeepalive).toString();
|
||||
}
|
||||
|
||||
public String getPreSharedKey() {
|
||||
return preSharedKey;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getResolvedEndpointString() throws UnknownHostException {
|
||||
if (endpoint == null)
|
||||
throw new UnknownHostException("{empty}");
|
||||
if (endpoint.isUnresolved())
|
||||
endpoint = new InetSocketAddress(endpoint.getHostString(), endpoint.getPort());
|
||||
if (endpoint.isUnresolved())
|
||||
throw new UnknownHostException(endpoint.getHostString());
|
||||
if (endpoint.getAddress() instanceof Inet6Address)
|
||||
return String.format(Locale.getDefault(),
|
||||
"[%s]:%d",
|
||||
endpoint.getAddress().getHostAddress(),
|
||||
endpoint.getPort());
|
||||
return String.format(Locale.getDefault(),
|
||||
"%s:%d",
|
||||
endpoint.getAddress().getHostAddress(),
|
||||
endpoint.getPort());
|
||||
}
|
||||
|
||||
public void parse(final String line) {
|
||||
final Attribute key = Attribute.match(line);
|
||||
switch (key) {
|
||||
case ALLOWED_IPS:
|
||||
addAllowedIPs(key.parseList(line));
|
||||
break;
|
||||
case ENDPOINT:
|
||||
setEndpointString(key.parse(line));
|
||||
break;
|
||||
case PERSISTENT_KEEPALIVE:
|
||||
setPersistentKeepaliveString(key.parse(line));
|
||||
break;
|
||||
case PRESHARED_KEY:
|
||||
setPreSharedKey(key.parse(line));
|
||||
break;
|
||||
case PUBLIC_KEY:
|
||||
setPublicKey(key.parse(line));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAllowedIPsString(final String allowedIPsString) {
|
||||
allowedIPsList.clear();
|
||||
addAllowedIPs(Attribute.stringToList(allowedIPsString));
|
||||
}
|
||||
|
||||
private void setEndpoint(final InetSocketAddress endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
private void setEndpointString(final String endpoint) {
|
||||
if (endpoint != null && !endpoint.isEmpty()) {
|
||||
final InetSocketAddress constructedEndpoint;
|
||||
if (endpoint.indexOf('/') != -1 || endpoint.indexOf('?') != -1 || endpoint.indexOf('#') != -1)
|
||||
throw new IllegalArgumentException("Forbidden characters in endpoint");
|
||||
final URI uri;
|
||||
try {
|
||||
uri = new URI("wg://" + endpoint);
|
||||
} catch (final URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
constructedEndpoint = InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort());
|
||||
setEndpoint(constructedEndpoint);
|
||||
} else
|
||||
setEndpoint(null);
|
||||
}
|
||||
|
||||
private void setPersistentKeepalive(final int persistentKeepalive) {
|
||||
this.persistentKeepalive = persistentKeepalive;
|
||||
}
|
||||
|
||||
private void setPersistentKeepaliveString(final String persistentKeepalive) {
|
||||
if (persistentKeepalive != null && !persistentKeepalive.isEmpty())
|
||||
setPersistentKeepalive(Integer.parseInt(persistentKeepalive, 10));
|
||||
else
|
||||
setPersistentKeepalive(0);
|
||||
}
|
||||
|
||||
private void setPreSharedKey(String preSharedKey) {
|
||||
if (preSharedKey != null && preSharedKey.isEmpty())
|
||||
preSharedKey = null;
|
||||
if (preSharedKey != null)
|
||||
KeyEncoding.keyFromBase64(preSharedKey);
|
||||
this.preSharedKey = preSharedKey;
|
||||
}
|
||||
|
||||
private void setPublicKey(String publicKey) {
|
||||
if (publicKey != null && publicKey.isEmpty())
|
||||
publicKey = null;
|
||||
if (publicKey != null)
|
||||
KeyEncoding.keyFromBase64(publicKey);
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder().append("[Peer]\n");
|
||||
if (!allowedIPsList.isEmpty())
|
||||
sb.append(Attribute.ALLOWED_IPS.composeWith(allowedIPsList));
|
||||
if (endpoint != null)
|
||||
sb.append(Attribute.ENDPOINT.composeWith(getEndpointString()));
|
||||
if (persistentKeepalive != 0)
|
||||
sb.append(Attribute.PERSISTENT_KEEPALIVE.composeWith(persistentKeepalive));
|
||||
if (preSharedKey != null)
|
||||
sb.append(Attribute.PRESHARED_KEY.composeWith(preSharedKey));
|
||||
if (publicKey != null)
|
||||
sb.append(Attribute.PUBLIC_KEY.composeWith(publicKey));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static class Observable extends BaseObservable implements Parcelable {
|
||||
public static final Creator<Observable> CREATOR = new Creator<Observable>() {
|
||||
@Override
|
||||
public Observable createFromParcel(final Parcel in) {
|
||||
return new Observable(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable[] newArray(final int size) {
|
||||
return new Observable[size];
|
||||
}
|
||||
};
|
||||
private String allowedIPs;
|
||||
private String endpoint;
|
||||
private String persistentKeepalive;
|
||||
private String preSharedKey;
|
||||
private String publicKey;
|
||||
|
||||
public Observable(final Peer parent) {
|
||||
loadData(parent);
|
||||
}
|
||||
|
||||
private Observable(final Parcel in) {
|
||||
allowedIPs = in.readString();
|
||||
endpoint = in.readString();
|
||||
persistentKeepalive = in.readString();
|
||||
preSharedKey = in.readString();
|
||||
publicKey = in.readString();
|
||||
}
|
||||
|
||||
public static Observable newInstance() {
|
||||
return new Observable(new Peer());
|
||||
}
|
||||
|
||||
public void commitData(final Peer parent) {
|
||||
parent.setAllowedIPsString(allowedIPs);
|
||||
parent.setEndpointString(endpoint);
|
||||
parent.setPersistentKeepaliveString(persistentKeepalive);
|
||||
parent.setPreSharedKey(preSharedKey);
|
||||
parent.setPublicKey(publicKey);
|
||||
if (parent.getPublicKey() == null)
|
||||
throw new IllegalArgumentException("Peer public key may not be empty");
|
||||
loadData(parent);
|
||||
notifyChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getAllowedIPs() {
|
||||
return allowedIPs;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getPersistentKeepalive() {
|
||||
return persistentKeepalive;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getPreSharedKey() {
|
||||
return preSharedKey;
|
||||
}
|
||||
|
||||
@Bindable
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
protected void loadData(final Peer parent) {
|
||||
allowedIPs = parent.getAllowedIPsString();
|
||||
endpoint = parent.getEndpointString();
|
||||
persistentKeepalive = parent.getPersistentKeepaliveString();
|
||||
preSharedKey = parent.getPreSharedKey();
|
||||
publicKey = parent.getPublicKey();
|
||||
}
|
||||
|
||||
public void setAllowedIPs(final String allowedIPs) {
|
||||
this.allowedIPs = allowedIPs;
|
||||
notifyPropertyChanged(BR.allowedIPs);
|
||||
}
|
||||
|
||||
public void setEndpoint(final String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
notifyPropertyChanged(BR.endpoint);
|
||||
}
|
||||
|
||||
public void setPersistentKeepalive(final String persistentKeepalive) {
|
||||
this.persistentKeepalive = persistentKeepalive;
|
||||
notifyPropertyChanged(BR.persistentKeepalive);
|
||||
}
|
||||
|
||||
public void setPreSharedKey(final String preSharedKey) {
|
||||
this.preSharedKey = preSharedKey;
|
||||
notifyPropertyChanged(BR.preSharedKey);
|
||||
}
|
||||
|
||||
public void setPublicKey(final String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
notifyPropertyChanged(BR.publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeString(allowedIPs);
|
||||
dest.writeString(endpoint);
|
||||
dest.writeString(persistentKeepalive);
|
||||
dest.writeString(preSharedKey);
|
||||
dest.writeString(publicKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
|
||||
/**
|
||||
* This is a specialized constant-time base64 and hex implementation that resists side-channel attacks.
|
||||
*/
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public final class KeyEncoding {
|
||||
public static final int KEY_LENGTH = 32;
|
||||
public static final int KEY_LENGTH_BASE64 = 44;
|
||||
public static final int KEY_LENGTH_HEX = 64;
|
||||
private static final String KEY_LENGTH_BASE64_EXCEPTION_MESSAGE =
|
||||
"WireGuard base64 keys must be 44 characters encoding 32 bytes";
|
||||
private static final String KEY_LENGTH_EXCEPTION_MESSAGE =
|
||||
"WireGuard keys must be 32 bytes";
|
||||
private static final String KEY_LENGTH_HEX_EXCEPTION_MESSAGE =
|
||||
"WireGuard hex keys must be 64 characters encoding 32 bytes";
|
||||
|
||||
private KeyEncoding() {
|
||||
// Prevent instantiation.
|
||||
}
|
||||
|
||||
private static int decodeBase64(final char[] src, final int src_offset) {
|
||||
int val = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
final char c = src[i + src_offset];
|
||||
val |= (-1
|
||||
+ ((((('A' - 1) - c) & (c - ('Z' + 1))) >>> 8) & (c - 64))
|
||||
+ ((((('a' - 1) - c) & (c - ('z' + 1))) >>> 8) & (c - 70))
|
||||
+ ((((('0' - 1) - c) & (c - ('9' + 1))) >>> 8) & (c + 5))
|
||||
+ ((((('+' - 1) - c) & (c - ('+' + 1))) >>> 8) & 63)
|
||||
+ ((((('/' - 1) - c) & (c - ('/' + 1))) >>> 8) & 64)
|
||||
) << (18 - 6 * i);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private static void encodeBase64(final byte[] src, final int src_offset,
|
||||
final char[] dest, final int dest_offset) {
|
||||
final byte[] input = {
|
||||
(byte) ((src[src_offset] >>> 2) & 63),
|
||||
(byte) ((src[src_offset] << 4 | ((src[1 + src_offset] & 0xff) >>> 4)) & 63),
|
||||
(byte) ((src[1 + src_offset] << 2 | ((src[2 + src_offset] & 0xff) >>> 6)) & 63),
|
||||
(byte) ((src[2 + src_offset]) & 63),
|
||||
};
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
dest[i + dest_offset] = (char) (input[i] + 'A'
|
||||
+ (((25 - input[i]) >>> 8) & 6)
|
||||
- (((51 - input[i]) >>> 8) & 75)
|
||||
- (((61 - input[i]) >>> 8) & 15)
|
||||
+ (((62 - input[i]) >>> 8) & 3));
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] keyFromBase64(final String str) {
|
||||
final char[] input = str.toCharArray();
|
||||
final byte[] key = new byte[KEY_LENGTH];
|
||||
if (input.length != KEY_LENGTH_BASE64 || input[KEY_LENGTH_BASE64 - 1] != '=')
|
||||
throw new IllegalArgumentException(KEY_LENGTH_BASE64_EXCEPTION_MESSAGE);
|
||||
int i;
|
||||
for (i = 0; i < KEY_LENGTH / 3; ++i) {
|
||||
final int val = decodeBase64(input, i * 4);
|
||||
if (val < 0)
|
||||
throw new IllegalArgumentException(KEY_LENGTH_BASE64_EXCEPTION_MESSAGE);
|
||||
key[i * 3] = (byte) ((val >>> 16) & 0xff);
|
||||
key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff);
|
||||
key[i * 3 + 2] = (byte) (val & 0xff);
|
||||
}
|
||||
final char[] endSegment = {
|
||||
input[i * 4],
|
||||
input[i * 4 + 1],
|
||||
input[i * 4 + 2],
|
||||
'A',
|
||||
};
|
||||
final int val = decodeBase64(endSegment, 0);
|
||||
if (val < 0 || (val & 0xff) != 0)
|
||||
throw new IllegalArgumentException(KEY_LENGTH_BASE64_EXCEPTION_MESSAGE);
|
||||
key[i * 3] = (byte) ((val >>> 16) & 0xff);
|
||||
key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff);
|
||||
return key;
|
||||
}
|
||||
|
||||
public static byte[] keyFromHex(final String str) {
|
||||
final char[] input = str.toCharArray();
|
||||
final byte[] key = new byte[KEY_LENGTH];
|
||||
if (input.length != KEY_LENGTH_HEX)
|
||||
throw new IllegalArgumentException(KEY_LENGTH_HEX_EXCEPTION_MESSAGE);
|
||||
|
||||
int c_acc = 0;
|
||||
int state = 0;
|
||||
|
||||
for (int i = 0; i < KEY_LENGTH_HEX; ++i) {
|
||||
final int c = input[i];
|
||||
final int c_num = c ^ 48;
|
||||
final int c_num0 = (c_num - 10) >> 8;
|
||||
final int c_alpha = (c & ~32) - 55;
|
||||
final int c_alpha0 = ((c_alpha - 10) ^ (c_alpha - 16)) >> 8;
|
||||
if ((c_num0 | c_alpha0) == 0)
|
||||
throw new IllegalArgumentException(KEY_LENGTH_HEX_EXCEPTION_MESSAGE);
|
||||
final int c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
|
||||
if (state == 0)
|
||||
c_acc = c_val * 16;
|
||||
else
|
||||
key[i / 2] = (byte) (c_acc | c_val);
|
||||
state = ~state;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
public static String keyToBase64(final byte[] key) {
|
||||
final char[] output = new char[KEY_LENGTH_BASE64];
|
||||
if (key.length != KEY_LENGTH)
|
||||
throw new IllegalArgumentException(KEY_LENGTH_EXCEPTION_MESSAGE);
|
||||
int i;
|
||||
for (i = 0; i < KEY_LENGTH / 3; ++i)
|
||||
encodeBase64(key, i * 3, output, i * 4);
|
||||
final byte[] endSegment = {
|
||||
key[i * 3],
|
||||
key[i * 3 + 1],
|
||||
0,
|
||||
};
|
||||
encodeBase64(endSegment, 0, output, i * 4);
|
||||
output[KEY_LENGTH_BASE64 - 1] = '=';
|
||||
return new String(output);
|
||||
}
|
||||
|
||||
public static String keyToHex(final byte[] key) {
|
||||
final char[] output = new char[KEY_LENGTH_HEX];
|
||||
if (key.length != KEY_LENGTH)
|
||||
throw new IllegalArgumentException(KEY_LENGTH_EXCEPTION_MESSAGE);
|
||||
for (int i = 0; i < KEY_LENGTH; ++i) {
|
||||
output[i * 2] = (char) (87 + (key[i] >> 4 & 0xf)
|
||||
+ ((((key[i] >> 4 & 0xf) - 10) >> 8) & ~38));
|
||||
output[i * 2 + 1] = (char) (87 + (key[i] & 0xf)
|
||||
+ ((((key[i] & 0xf) - 10) >> 8) & ~38));
|
||||
}
|
||||
return new String(output);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package com.wireguard.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Represents a Curve25519 keypair as used by WireGuard.
|
||||
*/
|
||||
|
||||
public class Keypair {
|
||||
private final byte[] privateKey;
|
||||
private final byte[] publicKey;
|
||||
|
||||
public Keypair() {
|
||||
this(generatePrivateKey());
|
||||
}
|
||||
|
||||
private Keypair(final byte[] privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
publicKey = generatePublicKey(privateKey);
|
||||
}
|
||||
|
||||
public Keypair(final String privateKey) {
|
||||
this(KeyEncoding.keyFromBase64(privateKey));
|
||||
}
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
private static byte[] generatePrivateKey() {
|
||||
final SecureRandom secureRandom = new SecureRandom();
|
||||
final byte[] privateKey = new byte[KeyEncoding.KEY_LENGTH];
|
||||
secureRandom.nextBytes(privateKey);
|
||||
privateKey[0] &= 248;
|
||||
privateKey[31] &= 127;
|
||||
privateKey[31] |= 64;
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
private static byte[] generatePublicKey(final byte[] privateKey) {
|
||||
final byte[] publicKey = new byte[KeyEncoding.KEY_LENGTH];
|
||||
Curve25519.eval(publicKey, 0, privateKey, null);
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return KeyEncoding.keyToBase64(privateKey);
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return KeyEncoding.keyToBase64(publicKey);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="4dp" />
|
||||
<padding
|
||||
android:bottom="4dp"
|
||||
android:left="8dp"
|
||||
android:right="8dp"
|
||||
android:top="4dp" />
|
||||
<solid android:color="?android:attr/colorPrimary" />
|
||||
</shape>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z" />
|
||||
</vector>
|
||||
@@ -1,29 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="400dp"
|
||||
android:height="400dp"
|
||||
android:viewportHeight="400.0"
|
||||
android:viewportWidth="400.0">
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M197.7,0C191.5,0.1 185.2,0.5 178.7,1.1C191.1,4 202.2,6.6 213.4,9.2C213.3,9.8 213.1,10.5 213,11.2C198.1,13.2 183.9,7.7 169.3,5.7C174.6,8.8 180,11.7 185.5,14.2C191.2,16.7 197,18.8 202.8,21.2C195.4,27.5 188,28.8 178.7,26.7C173.7,25.6 168.3,25 163.1,25.2C157.8,25.5 152.4,26.8 147.5,30C152.7,32.7 157.5,34.8 162.1,37.5C163.9,38.6 166.1,40.4 166.6,42.4C167.8,46.9 168.2,51.8 168.9,56.5C160.4,57.5 145.4,66.1 142.4,71.7C155.5,74.3 169.7,71.2 182.2,79.6C178.1,82.8 168.5,86.6 165,89.3C169.3,90.4 179.4,89.9 183.3,89.6C186.6,89.4 188.2,89.3 189.5,90.4L228,120.6C232.1,123.8 248.4,139.3 252.7,149C256.3,157.3 256.8,164.4 256.8,166.1C256.8,170.7 256.2,177.9 253,186C252.7,186.9 252,188.3 251.2,189.8L280.8,219.4C288.3,207.7 293.4,194.5 296.3,180C299.6,163.2 299.4,146.5 291.7,130.5C285.8,118.3 276.1,109.4 265.8,101.3C255.1,93 243.7,85.4 233.1,77C230.2,74.8 228.3,70.8 226.9,67.3C226.4,65.8 228.2,61.7 229.4,61.5C236,60.3 242.6,59.7 249.3,59.4C256.9,59.2 264.6,59.4 272.3,59.5C274,59.5 276.2,59.3 277.2,60.3C281.2,64.2 284.3,61.6 287,59.1C289.4,56.9 291,54 292.9,51.6C291.7,51.4 289.4,50.9 287.1,50.8C279.4,50.6 271.7,50.7 264,50.5C262.6,50.4 261.3,49 259.9,48.2C261.3,47.6 262.8,46.6 264.2,46.6C277.5,46.5 290.8,46.5 304.1,46.5C304.2,39.6 294.9,30.1 286.7,27.5C286.6,28.5 286.5,29.3 286.5,30.3C278.3,30.5 270.3,30.3 263,26.4C261.1,25.4 259.8,23.1 258.3,21.5C256.3,19.3 254.7,16.6 252.3,15.2C247.4,12.3 242,10.3 236.9,7.8C224.3,1.7 211.3,-0.1 197.7,0zM249.6,29.4C250.3,29.4 250.9,29.6 251.6,30.2C252.6,31 253.6,31.9 254.8,33C253.3,33.8 252,34.5 250.8,35.1C249,36 247.6,35.4 246.5,34C245.6,32.8 245.5,31.7 246.8,30.7C247.7,30 248.6,29.4 249.6,29.4zM231.8,213C220.7,221.9 207.6,227.2 193.6,230.1C152.7,238.5 118.7,282.2 128.3,330.3C139.5,386.5 201.5,416.9 252.2,390.2C285,372.9 302.3,339.2 297.7,302.5C295.9,288.1 291,275.4 283.1,264.3L265.1,246.3C264.2,246.2 263.2,246.6 261.9,247.5C253.1,253.1 244.2,258.4 235.1,263.4C229.9,266.2 224.2,268.3 217.7,271.2C219.9,271.8 221,272.1 222.1,272.4C246.5,278.9 259.6,300.3 253.8,324.4C248.7,345.7 227,359.4 205.9,355.8C188.4,352.8 173.1,338.2 170.5,320.8C167.8,301.8 177.2,283.5 194.1,275.9C203.4,271.6 213,267.9 222.3,263.6C232.9,258.7 244.4,254.9 253.6,248.1C256.2,246.2 258.7,244.2 261,242.2L231.8,213z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1.3750788" />
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m97.9,307.6c-7.8,2 -15.4,4.9 -23.4,7.5 3.9,-26.3 34.7,-50.6 60.8,-47.8 -8.1,10.9 -11.8,23.3 -12.7,35.6 -8.7,1.6 -16.8,2.7 -24.8,4.7"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1.3750788" />
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M177,111.6C175.7,111.6 174.4,111.7 173.1,111.7L247,185.6C248.1,183.2 249,180.6 249.9,177.9C252.2,170.4 251.8,159.3 248,152.1C234.7,126.6 206.6,111.3 177,111.6zM139.7,120.9C137.9,121.8 136.1,122.9 134.3,124C81.2,156.4 84.3,231 132.9,260.7C136.6,263 138.3,262.7 140.8,259.4C148.4,249.2 157.9,241.1 169,234.9C171.7,233.3 174.5,231.9 178.1,230C154,225.8 141.1,214.8 139.6,197.8C137.9,179.4 146.9,164.5 163.2,158.3C167,156.9 171,156.2 174.9,156.1L139.7,120.9zM212.5,193.7C212.5,195.8 212.3,197.9 211.8,200.1C209.9,210.5 203.6,218.1 195.8,224.8C208.5,221.9 219.6,216.9 228.6,209.7L212.5,193.7z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1.3750788" />
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M78.4,59.6l262,262l-21.3,21.3l-262,-262z"
|
||||
android:strokeWidth="0.82064575" />
|
||||
</vector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/list_item_activated" android:state_activated="true" />
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</selector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/list_item_ripple">
|
||||
<item android:drawable="@drawable/list_item_background" />
|
||||
</ripple>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/master_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:ignore="MergeRootFrame" />
|
||||
@@ -1,137 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.fragment.TunnelController" />
|
||||
|
||||
<import type="com.wireguard.android.model.Tunnel.State" />
|
||||
|
||||
<import type="com.wireguard.android.util.ClipboardUtils" />
|
||||
|
||||
<variable
|
||||
name="tunnel"
|
||||
type="com.wireguard.android.model.Tunnel" />
|
||||
|
||||
<variable
|
||||
name="config"
|
||||
type="com.wireguard.config.Config.Observable" />
|
||||
</data>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="2dp"
|
||||
app:contentPadding="8dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interface_title"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toStartOf="@+id/tunnel_switch"
|
||||
android:text="@string/interface_title" />
|
||||
|
||||
<com.wireguard.android.widget.ToggleSwitch
|
||||
android:id="@+id/tunnel_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/interface_title"
|
||||
android:layout_alignParentEnd="true"
|
||||
app:checked="@{tunnel.state == State.UP}"
|
||||
app:onBeforeCheckedChanged="@{TunnelController::setTunnelState}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interface_name_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/interface_title"
|
||||
android:layout_marginTop="8dp"
|
||||
android:labelFor="@+id/interface_name_text"
|
||||
android:text="@string/name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interface_name_text"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/interface_name_label"
|
||||
android:text="@{tunnel.name}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/public_key_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/interface_name_text"
|
||||
android:layout_marginTop="8dp"
|
||||
android:labelFor="@+id/public_key_text"
|
||||
android:text="@string/public_key" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/public_key_text"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/public_key_label"
|
||||
android:contentDescription="@string/public_key_description"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:onClick="@{ClipboardUtils::copyTextView}"
|
||||
android:text="@{config.interfaceSection.publicKey}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/addresses_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/public_key_text"
|
||||
android:layout_marginTop="8dp"
|
||||
android:labelFor="@+id/addresses_text"
|
||||
android:text="@string/addresses" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/addresses_text"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/addresses_label"
|
||||
android:contentDescription="@string/addresses"
|
||||
android:text="@{config.interfaceSection.addresses}" />
|
||||
</RelativeLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:divider="@null"
|
||||
android:orientation="vertical"
|
||||
app:items="@{config.peers}"
|
||||
app:layout="@{@layout/tunnel_detail_peer}"
|
||||
tools:ignore="UselessLeaf" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</layout>
|
||||
@@ -1,94 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.util.ClipboardUtils" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.wireguard.config.Peer.Observable" />
|
||||
</data>
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="2dp"
|
||||
app:contentPadding="8dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/peer_title"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:text="@string/peer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/public_key_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/peer_title"
|
||||
android:layout_marginTop="8dp"
|
||||
android:labelFor="@+id/public_key_text"
|
||||
android:text="@string/public_key" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/public_key_text"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/public_key_label"
|
||||
android:contentDescription="@string/public_key_description"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:onClick="@{ClipboardUtils::copyTextView}"
|
||||
android:text="@{item.publicKey}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/allowed_ips_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/public_key_text"
|
||||
android:layout_marginTop="8dp"
|
||||
android:labelFor="@+id/allowed_ips_text"
|
||||
android:text="@string/allowed_ips" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/allowed_ips_text"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/allowed_ips_label"
|
||||
android:text="@{item.allowedIPs}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/endpoint_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/allowed_ips_text"
|
||||
android:layout_marginTop="8dp"
|
||||
android:labelFor="@+id/endpoint_text"
|
||||
android:text="@string/endpoint" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/endpoint_text"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/endpoint_label"
|
||||
android:text="@{item.endpoint}" />
|
||||
</RelativeLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
</layout>
|
||||
@@ -1,236 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.util.ClipboardUtils" />
|
||||
|
||||
<import type="com.wireguard.android.widget.KeyInputFilter" />
|
||||
|
||||
<import type="com.wireguard.android.widget.NameInputFilter" />
|
||||
|
||||
<import type="com.wireguard.config.Peer" />
|
||||
|
||||
<variable
|
||||
name="config"
|
||||
type="com.wireguard.config.Config.Observable" />
|
||||
</data>
|
||||
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/main_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="2dp"
|
||||
app:contentPadding="8dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interface_title"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:text="@string/interface_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interface_name_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/interface_title"
|
||||
android:layout_marginTop="8dp"
|
||||
android:labelFor="@+id/interface_name_text"
|
||||
android:text="@string/name" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/interface_name_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/interface_name_label"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
android:text="@={config.name}"
|
||||
app:filter="@{NameInputFilter.newInstance()}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/private_key_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/interface_name_text"
|
||||
android:labelFor="@+id/private_key_text"
|
||||
android:text="@string/private_key" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/private_key_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/private_key_label"
|
||||
android:layout_toStartOf="@+id/generate_private_key_button"
|
||||
android:contentDescription="@string/public_key_description"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
android:text="@={config.interfaceSection.privateKey}"
|
||||
app:filter="@{KeyInputFilter.newInstance()}" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/generate_private_key_button"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/private_key_text"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_below="@+id/private_key_label"
|
||||
android:onClick="@{() -> config.interfaceSection.generateKeypair()}"
|
||||
android:text="@string/generate" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/public_key_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/private_key_text"
|
||||
android:labelFor="@+id/public_key_text"
|
||||
android:text="@string/public_key" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/public_key_text"
|
||||
style="?android:attr/editTextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/public_key_label"
|
||||
android:contentDescription="@string/public_key_description"
|
||||
android:ellipsize="end"
|
||||
android:focusable="false"
|
||||
android:hint="@string/hint_generated"
|
||||
android:maxLines="1"
|
||||
android:onClick="@{ClipboardUtils::copyTextView}"
|
||||
android:text="@{config.interfaceSection.publicKey}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/addresses_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/public_key_text"
|
||||
android:layout_toStartOf="@+id/listen_port_label"
|
||||
android:labelFor="@+id/addresses_text"
|
||||
android:text="@string/addresses" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/addresses_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/addresses_label"
|
||||
android:layout_toStartOf="@+id/listen_port_text"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
android:text="@={config.interfaceSection.addresses}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/listen_port_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/addresses_label"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignStart="@+id/generate_private_key_button"
|
||||
android:labelFor="@+id/listen_port_text"
|
||||
android:text="@string/listen_port" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/listen_port_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/addresses_text"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignStart="@+id/generate_private_key_button"
|
||||
android:hint="@string/hint_random"
|
||||
android:inputType="number"
|
||||
android:text="@={config.interfaceSection.listenPort}"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dns_servers_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/addresses_text"
|
||||
android:layout_toStartOf="@+id/mtu_label"
|
||||
android:labelFor="@+id/dns_servers_text"
|
||||
android:text="@string/dns_servers" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dns_servers_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/dns_servers_label"
|
||||
android:layout_toStartOf="@+id/mtu_text"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
android:text="@={config.interfaceSection.dnses}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mtu_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/dns_servers_label"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignStart="@+id/generate_private_key_button"
|
||||
android:labelFor="@+id/mtu_text"
|
||||
android:text="@string/mtu" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/mtu_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/dns_servers_text"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignStart="@+id/generate_private_key_button"
|
||||
android:hint="@string/hint_automatic"
|
||||
android:inputType="number"
|
||||
android:text="@={config.interfaceSection.mtu}"
|
||||
android:textAlignment="center" />
|
||||
</RelativeLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@null"
|
||||
android:orientation="vertical"
|
||||
app:items="@{config.peers}"
|
||||
app:layout="@{@layout/tunnel_editor_peer}"
|
||||
tools:ignore="UselessLeaf" />
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:onClick="@{() -> config.peers.add(Peer.Observable.newInstance())}"
|
||||
android:text="@string/add_peer" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
@@ -1,148 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.widget.KeyInputFilter" />
|
||||
|
||||
<variable
|
||||
name="collection"
|
||||
type="android.databinding.ObservableList<com.wireguard.config.Peer.Observable>" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.wireguard.config.Peer.Observable" />
|
||||
</data>
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="2dp"
|
||||
app:contentPadding="8dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/peer_title"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_toStartOf="@+id/peer_action_delete"
|
||||
android:text="@string/peer" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/peer_action_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="@null"
|
||||
android:contentDescription="@string/delete"
|
||||
android:onClick="@{() -> collection.remove(item)}"
|
||||
android:src="@drawable/ic_action_delete_black" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/public_key_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/peer_title"
|
||||
android:labelFor="@+id/public_key_text"
|
||||
android:text="@string/public_key" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/public_key_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/public_key_label"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
android:text="@={item.publicKey}"
|
||||
app:filter="@{KeyInputFilter.newInstance()}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pre_shared_key_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/public_key_text"
|
||||
android:labelFor="@+id/pre_shared_key_text"
|
||||
android:text="@string/pre_shared_key" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/pre_shared_key_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/pre_shared_key_label"
|
||||
android:hint="@string/hint_optional"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
android:text="@={item.preSharedKey}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/allowed_ips_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/pre_shared_key_text"
|
||||
android:labelFor="@+id/allowed_ips_text"
|
||||
android:text="@string/allowed_ips" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/allowed_ips_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/allowed_ips_label"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
android:text="@={item.allowedIPs}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/endpoint_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/allowed_ips_text"
|
||||
android:layout_toStartOf="@+id/persistent_keepalive_label"
|
||||
android:labelFor="@+id/endpoint_text"
|
||||
android:text="@string/endpoint" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/endpoint_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/endpoint_label"
|
||||
android:layout_toStartOf="@+id/persistent_keepalive_text"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
android:text="@={item.endpoint}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/persistent_keepalive_label"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/endpoint_label"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:labelFor="@+id/persistent_keepalive_text"
|
||||
android:text="@string/persistent_keepalive" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/persistent_keepalive_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/endpoint_text"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignStart="@+id/persistent_keepalive_label"
|
||||
android:hint="@string/hint_optional"
|
||||
android:inputType="number"
|
||||
android:text="@={item.persistentKeepalive}"
|
||||
android:textAlignment="center" />
|
||||
</RelativeLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
</layout>
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.model.Tunnel" />
|
||||
|
||||
<variable
|
||||
name="fragment"
|
||||
type="com.wireguard.android.fragment.TunnelListFragment" />
|
||||
|
||||
<variable
|
||||
name="tunnels"
|
||||
type="com.wireguard.android.util.ObservableKeyedList<String, Tunnel>" />
|
||||
</data>
|
||||
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/main_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/tunnel_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:choiceMode="multipleChoiceModal"
|
||||
app:items="@{tunnels}"
|
||||
app:layout="@{@layout/tunnel_list_item}" />
|
||||
|
||||
<com.getbase.floatingactionbutton.FloatingActionsMenu
|
||||
android:id="@+id/create_menu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="8dp"
|
||||
app:fab_labelStyle="@style/fab_label"
|
||||
app:fab_labelsPosition="left"
|
||||
app:layout_dodgeInsetEdges="bottom">
|
||||
|
||||
<com.getbase.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/create_empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{fragment::onRequestCreateConfig}"
|
||||
app:fab_icon="@drawable/ic_action_edit"
|
||||
app:fab_size="mini"
|
||||
app:fab_title="@string/create_empty" />
|
||||
|
||||
<com.getbase.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/create_from_file"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{fragment::onRequestImportConfig}"
|
||||
app:fab_icon="@drawable/ic_action_open"
|
||||
app:fab_size="mini"
|
||||
app:fab_title="@string/create_from_file" />
|
||||
</com.getbase.floatingactionbutton.FloatingActionsMenu>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
@@ -1,54 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.fragment.TunnelController" />
|
||||
|
||||
<import type="com.wireguard.android.model.Tunnel" />
|
||||
|
||||
<import type="com.wireguard.android.model.Tunnel.State" />
|
||||
|
||||
<variable
|
||||
name="collection"
|
||||
type="com.wireguard.android.util.ObservableKeyedList<String, Tunnel>" />
|
||||
|
||||
<variable
|
||||
name="key"
|
||||
type="String" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.wireguard.android.model.Tunnel" />
|
||||
</data>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/list_item_background_anim"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tunnel_name"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toStartOf="@+id/tunnel_switch"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{key}" />
|
||||
|
||||
<com.wireguard.android.widget.ToggleSwitch
|
||||
android:id="@+id/tunnel_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/tunnel_name"
|
||||
android:layout_alignParentEnd="true"
|
||||
app:checked="@{item.state == State.UP}"
|
||||
app:onBeforeCheckedChanged="@{TunnelController::setTunnelState}" />
|
||||
</RelativeLayout>
|
||||
</layout>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="list_item_activated">#cfd8dc</color> <!-- Blue Grey 200 -->
|
||||
<color name="list_item_ripple">#808e95</color> <!-- Blue Grey 200 dark -->
|
||||
</resources>
|
||||
@@ -1,75 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="delete_error">
|
||||
<item quantity="one">Unable to delete %d tunnel: %s</item>
|
||||
<item quantity="other">Unable to delete %d tunnels: %s</item>
|
||||
</plurals>
|
||||
<plurals name="delete_success">
|
||||
<item quantity="one">Successfully deleted %d tunnel</item>
|
||||
<item quantity="other">Successfully deleted %d tunnels</item>
|
||||
</plurals>
|
||||
<plurals name="delete_title">
|
||||
<item quantity="one">%d tunnel selected</item>
|
||||
<item quantity="other">%d tunnels selected</item>
|
||||
</plurals>
|
||||
<plurals name="import_partial_success">
|
||||
<item quantity="one">Imported %d of %d tunnels</item>
|
||||
<item quantity="other">Imported %d of %d tunnels</item>
|
||||
</plurals>
|
||||
<plurals name="import_total_success">
|
||||
<item quantity="one">Imported %d tunnel</item>
|
||||
<item quantity="other">Imported %d tunnels</item>
|
||||
</plurals>
|
||||
<string name="add_peer">Add peer</string>
|
||||
<string name="addresses">Addresses</string>
|
||||
<string name="allowed_ips">Allowed IPs</string>
|
||||
<string name="app_name">WireGuard</string>
|
||||
<string name="config_save_error">Unable to save configuration for “%s”: %s</string>
|
||||
<string name="config_save_success">Successfully saved configuration for “%s”</string>
|
||||
<string name="create_activity_title">Create WireGuard Tunnel</string>
|
||||
<string name="create_empty">Create from scratch</string>
|
||||
<string name="create_from_file">Create from file or archive</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="dns_servers">DNS servers</string>
|
||||
<string name="edit">Edit</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="error_down">Error bringing down tunnel: %s</string>
|
||||
<string name="error_root">Please obtain root access and try again</string>
|
||||
<string name="error_up">Error bringing up tunnel: %s</string>
|
||||
<string name="export_error">Unable to export tunnels: %s</string>
|
||||
<string name="export_success">Saved to %s</string>
|
||||
<string name="export_summary">Zip file will be saved to downloads folder</string>
|
||||
<string name="generate">Generate</string>
|
||||
<string name="hint_automatic">(auto)</string>
|
||||
<string name="hint_generated">(generated)</string>
|
||||
<string name="hint_optional">(optional)</string>
|
||||
<string name="hint_random">(random)</string>
|
||||
<string name="import_error">Unable to import tunnel: %s</string>
|
||||
<string name="import_success">Imported “%s”</string>
|
||||
<string name="interface_title">Interface</string>
|
||||
<string name="listen_port">Listen port</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="peer">Peer</string>
|
||||
<string name="persistent_keepalive">Persistent keepalive</string>
|
||||
<string name="pre_shared_key">Pre-shared key</string>
|
||||
<string name="private_key">Private key</string>
|
||||
<string name="public_key">Public key</string>
|
||||
<string name="public_key_description">Public key</string>
|
||||
<string name="restore_on_boot_summary">Bring up previously-enabled tunnels on boot</string>
|
||||
<string name="restore_on_boot_title">Restore on boot</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="toggle_error">Error toggling WireGuard tunnel: %s</string>
|
||||
<string name="tools_installer_already">wg and wg-quick are already installed</string>
|
||||
<string name="tools_installer_failure">Unable to install command-line tools (no root?)</string>
|
||||
<string name="tools_installer_initial">Install optional tools for scripting into the system partition</string>
|
||||
<string name="tools_installer_success">wg and wg-quick installed into the system partition</string>
|
||||
<string name="tools_installer_title">Install command line tools</string>
|
||||
<string name="tools_installer_working">Installing wg and wg-quick into the system partition</string>
|
||||
<string name="tunnel_create_error">Unable to create tunnel: %s</string>
|
||||
<string name="tunnel_create_success">Successfully created tunnel “%s”</string>
|
||||
<string name="tunnel_rename_error">Unable to rename tunnel: %s</string>
|
||||
<string name="tunnel_rename_success">Successfully renamed tunnel to “%s”</string>
|
||||
<string name="zip_exporter_title">Export tunnels to zip file</string>
|
||||
</resources>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar" />
|
||||
|
||||
<style name="SettingsTheme" parent="AppTheme">
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
|
||||
</style>
|
||||
|
||||
<style name="fab_label" parent="android:TextAppearance.DeviceDefault.Inverse">
|
||||
<item name="android:background">@drawable/fab_label_background</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="restore_on_boot"
|
||||
android:summary="@string/restore_on_boot_summary"
|
||||
android:title="@string/restore_on_boot_title" />
|
||||
<com.wireguard.android.preference.ToolsInstallerPreference android:key="tools_installer" />
|
||||
<com.wireguard.android.preference.ZipExporterPreference android:key="zip_exporter" />
|
||||
</PreferenceScreen>
|
||||
@@ -1,29 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
|
||||
|
||||
# Work around https://github.com/android-ndk/ndk/issues/602
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
|
||||
|
||||
add_executable(libwg-quick.so wireguard/src/tools/wg-quick/android.c)
|
||||
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -pedantic -Wno-missing-field-initializers)
|
||||
|
||||
file(GLOB WG_SOURCES wireguard/src/tools/*.c libmnl/src/*.c)
|
||||
add_executable(libwg.so ${WG_SOURCES})
|
||||
target_compile_options(libwg.so PUBLIC -idirafter "${CMAKE_CURRENT_SOURCE_DIR}/libmnl/include/" -I "${CMAKE_CURRENT_SOURCE_DIR}/wireguard/src/tools/" -O3 -std=gnu11 -D_GNU_SOURCE -DHAVE_VISIBILITY_HIDDEN -DRUNSTATEDIR=\"\\\"/data/data/com.wireguard.android/cache\\\"\" -Wno-pointer-arith -Wno-unused-parameter)
|
||||
|
||||
add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building wireguard-go" VERBATIM COMMAND make
|
||||
ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME}
|
||||
ANDROID_C_COMPILER=${ANDROID_C_COMPILER}
|
||||
ANDROID_TOOLCHAIN_ROOT=${ANDROID_TOOLCHAIN_ROOT}
|
||||
ANDROID_LLVM_TRIPLE=${ANDROID_LLVM_TRIPLE}
|
||||
ANDROID_SYSROOT=${ANDROID_SYSROOT}
|
||||
CFLAGS=${CMAKE_C_FLAGS}\ -Wno-unused-command-line-argument
|
||||
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}\ -fuse-ld=gold
|
||||
DESTDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
)
|
||||
# Hack to make it actually build as part of the default target
|
||||
add_dependencies(libwg.so libwg-go.so)
|
||||
Submodule app/tools/libmnl deleted from 0930a63252
@@ -1,2 +0,0 @@
|
||||
src/
|
||||
.gobuildversion
|
||||
@@ -1,58 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
|
||||
FILES := $(filter-out %/main.go,$(wildcard ../wireguard-go/*/*.go) $(wildcard ../wireguard-go/*.go))
|
||||
|
||||
NDK_GO_ARCH_MAP_x86 := 386
|
||||
NDK_GO_ARCH_MAP_x86_64 := amd64
|
||||
NDK_GO_ARCH_MAP_arm := arm
|
||||
NDK_GO_ARCH_MAP_arm64 := arm64
|
||||
NDK_GO_ARCH_MAP_mips := mipsx
|
||||
NDK_GO_ARCH_MAP_mips64 := mips64x
|
||||
|
||||
CLANG_FLAGS := --target=$(ANDROID_LLVM_TRIPLE) --gcc-toolchain=$(ANDROID_TOOLCHAIN_ROOT) --sysroot=$(ANDROID_SYSROOT)
|
||||
export CGO_CFLAGS := $(CLANG_FLAGS) $(CFLAGS)
|
||||
export CGO_LDFLAGS := $(CLANG_FLAGS) $(LDFLAGS)
|
||||
export CC := $(ANDROID_C_COMPILER)
|
||||
export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
|
||||
export GOOS := android
|
||||
export CGO_ENABLED := 1
|
||||
|
||||
default: $(DESTDIR)/libwg-go.so
|
||||
|
||||
GOBUILDARCH := $(NDK_GO_ARCH_MAP_$(shell uname -m))
|
||||
GOBUILDOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
||||
GOBUILDVERSION := 1.10.2
|
||||
GOBUILDTARBALL := https://dl.google.com/go/go$(GOBUILDVERSION).$(GOBUILDOS)-$(GOBUILDARCH).tar.gz
|
||||
GOBUILDVERSION_NEEDED := go version go$(GOBUILDVERSION) $(GOBUILDOS)/$(GOBUILDARCH)
|
||||
|
||||
GOBUILDVERSION_CURRENT := $(shell go version 2>/dev/null)
|
||||
ifeq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_CURRENT))
|
||||
$(info Using system go)
|
||||
else
|
||||
export GOROOT := $(CURDIR)/goroot
|
||||
export PATH := $(GOROOT)/bin:$(PATH)
|
||||
GOBUILDVERSION_CURRENT := $(shell $(GOROOT)/bin/go version 2>/dev/null)
|
||||
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_CURRENT))
|
||||
$(shell rm -f $(GOROOT)/bin/go)
|
||||
endif
|
||||
$(GOROOT)/bin/go:
|
||||
rm -rf "$(GOROOT)"
|
||||
mkdir -p "$(GOROOT)"
|
||||
curl "$(GOBUILDTARBALL)" | tar -C "$(GOROOT)" --strip-components=1 -xzf - || { rm -rf "$(GOROOT)"; exit 1; }
|
||||
patch -p1 -f -N -r- -d"$(GOROOT)" < goruntime-boottime-over-monotonic.diff || { rm -rf "$(GOROOT)"; exit 1; }
|
||||
$(DESTDIR)/libwg-go.so: $(GOROOT)/bin/go
|
||||
endif
|
||||
|
||||
$(shell test "$$(cat .gobuildversion 2>/dev/null)" = "$(GOBUILDVERSION_CURRENT)" || rm -f "$(DESTDIR)/libwg-go.so")
|
||||
|
||||
$(DESTDIR)/libwg-go.so: $(FILES) src/git.zx2c4.com/wireguard-go/api-android.go src/git.zx2c4.com/wireguard-go/tun/api-android.go src/git.zx2c4.com/wireguard-go/jni.c
|
||||
find . -name '*.go' -type l -delete
|
||||
find . -type d -empty -delete
|
||||
mkdir -p $(subst ../wireguard-go/,./src/git.zx2c4.com/wireguard-go/,$(dir $(FILES)))
|
||||
$(foreach FILE,$(FILES),ln -sfrt $(subst ../wireguard-go/,./src/git.zx2c4.com/wireguard-go/,$(dir $(FILE))) $(FILE);)
|
||||
patch -p1 -f -R -s --dry-run -d"$$(go env GOROOT)" < goruntime-boottime-over-monotonic.diff
|
||||
GOPATH=$(PWD) go get -v -d git.zx2c4.com/wireguard-go
|
||||
GOPATH=$(PWD) go build -v -o $(DESTDIR)/libwg-go.so -buildmode c-shared git.zx2c4.com/wireguard-go
|
||||
go version > .gobuildversion
|
||||
@@ -1,103 +0,0 @@
|
||||
diff -ru b/src/runtime/sys_linux_386.s a/src/runtime/sys_linux_386.s
|
||||
--- b/src/runtime/sys_linux_386.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_386.s 2018-05-09 17:09:42.319531743 +0200
|
||||
@@ -287,13 +287,13 @@
|
||||
|
||||
LEAL 8(SP), BX // &ts (struct timespec)
|
||||
MOVL BX, 4(SP)
|
||||
- MOVL $1, 0(SP) // CLOCK_MONOTONIC
|
||||
+ MOVL $7, 0(SP) // CLOCK_BOOTTIME
|
||||
CALL AX
|
||||
JMP finish
|
||||
|
||||
fallback:
|
||||
MOVL $SYS_clock_gettime, AX
|
||||
- MOVL $1, BX // CLOCK_MONOTONIC
|
||||
+ MOVL $7, BX // CLOCK_BOOTTIME
|
||||
LEAL 8(SP), CX
|
||||
INVOKE_SYSCALL
|
||||
|
||||
diff -ru b/src/runtime/sys_linux_amd64.s a/src/runtime/sys_linux_amd64.s
|
||||
--- b/src/runtime/sys_linux_amd64.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_amd64.s 2018-05-09 17:09:42.319531743 +0200
|
||||
@@ -258,7 +258,7 @@
|
||||
MOVQ runtime·__vdso_clock_gettime_sym(SB), AX
|
||||
CMPQ AX, $0
|
||||
JEQ fallback
|
||||
- MOVL $1, DI // CLOCK_MONOTONIC
|
||||
+ MOVL $7, DI // CLOCK_BOOTTIME
|
||||
LEAQ 0(SP), SI
|
||||
CALL AX
|
||||
MOVQ 0(SP), AX // sec
|
||||
diff -ru b/src/runtime/sys_linux_arm64.s a/src/runtime/sys_linux_arm64.s
|
||||
--- b/src/runtime/sys_linux_arm64.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_arm64.s 2018-05-09 17:12:47.844591628 +0200
|
||||
@@ -201,7 +201,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$24-8
|
||||
- MOVW $1, R0 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R0 // CLOCK_BOOTTIME
|
||||
MOVD RSP, R1
|
||||
MOVD $SYS_clock_gettime, R8
|
||||
SVC
|
||||
diff -ru b/src/runtime/sys_linux_arm.s a/src/runtime/sys_linux_arm.s
|
||||
--- b/src/runtime/sys_linux_arm.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_arm.s 2018-05-09 17:13:18.983281049 +0200
|
||||
@@ -235,7 +235,7 @@
|
||||
|
||||
// int64 nanotime(void)
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$32
|
||||
- MOVW $1, R0 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R0 // CLOCK_BOOTTIME
|
||||
MOVW $8(R13), R1 // timespec
|
||||
MOVW $SYS_clock_gettime, R7
|
||||
SWI $0
|
||||
diff -ru b/src/runtime/sys_linux_mips64x.s a/src/runtime/sys_linux_mips64x.s
|
||||
--- b/src/runtime/sys_linux_mips64x.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_mips64x.s 2018-05-09 17:09:42.321531721 +0200
|
||||
@@ -197,7 +197,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$16
|
||||
- MOVW $1, R4 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R4 // CLOCK_BOOTTIME
|
||||
MOVV $0(R29), R5
|
||||
MOVV $SYS_clock_gettime, R2
|
||||
SYSCALL
|
||||
diff -ru b/src/runtime/sys_linux_mipsx.s a/src/runtime/sys_linux_mipsx.s
|
||||
--- b/src/runtime/sys_linux_mipsx.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_mipsx.s 2018-05-09 17:09:42.321531721 +0200
|
||||
@@ -202,7 +202,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$8-8
|
||||
- MOVW $1, R4 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R4 // CLOCK_BOOTTIME
|
||||
MOVW $4(R29), R5
|
||||
MOVW $SYS_clock_gettime, R2
|
||||
SYSCALL
|
||||
diff -ru b/src/runtime/sys_linux_ppc64x.s a/src/runtime/sys_linux_ppc64x.s
|
||||
--- b/src/runtime/sys_linux_ppc64x.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_ppc64x.s 2018-05-09 17:09:42.322531710 +0200
|
||||
@@ -176,7 +176,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$16
|
||||
- MOVW $1, R3 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R3 // CLOCK_BOOTTIME
|
||||
MOVD $0(R1), R4
|
||||
SYSCALL $SYS_clock_gettime
|
||||
MOVD 0(R1), R3 // sec
|
||||
diff -ru b/src/runtime/sys_linux_s390x.s a/src/runtime/sys_linux_s390x.s
|
||||
--- b/src/runtime/sys_linux_s390x.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_s390x.s 2018-05-09 17:09:42.322531710 +0200
|
||||
@@ -188,7 +188,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$16
|
||||
- MOVW $1, R2 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R2 // CLOCK_BOOTTIME
|
||||
MOVD $tp-16(SP), R3
|
||||
MOVW $SYS_clock_gettime, R1
|
||||
SYSCALL
|
||||
@@ -1,162 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
// #cgo LDFLAGS: -llog
|
||||
// #include <android/log.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"git.zx2c4.com/wireguard-go/tun"
|
||||
"golang.org/x/sys/unix"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type AndroidLogger struct {
|
||||
level C.int
|
||||
interfaceName string
|
||||
}
|
||||
|
||||
func (l AndroidLogger) Write(p []byte) (int, error) {
|
||||
C.__android_log_write(l.level, C.CString("WireGuard/GoBackend/"+l.interfaceName), C.CString(string(p)))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var tunnelHandles map[int32]*Device
|
||||
|
||||
func init() {
|
||||
tunnelHandles = make(map[int32]*Device)
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, unix.SIGUSR2)
|
||||
go func() {
|
||||
buf := make([]byte, os.Getpagesize())
|
||||
for {
|
||||
select {
|
||||
case <-signals:
|
||||
n := runtime.Stack(buf, true)
|
||||
buf[n] = 0
|
||||
C.__android_log_write(C.ANDROID_LOG_ERROR, C.CString("WireGuard/GoBackend/Stacktrace"), (*_Ctype_char)(unsafe.Pointer(&buf[0])))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//export wgTurnOn
|
||||
func wgTurnOn(ifnameRef string, tun_fd int32, settings string) int32 {
|
||||
interfaceName := string([]byte(ifnameRef))
|
||||
|
||||
logger := &Logger{
|
||||
Debug: log.New(&AndroidLogger{level: C.ANDROID_LOG_DEBUG, interfaceName: interfaceName}, "", 0),
|
||||
Info: log.New(&AndroidLogger{level: C.ANDROID_LOG_INFO, interfaceName: interfaceName}, "", 0),
|
||||
Error: log.New(&AndroidLogger{level: C.ANDROID_LOG_ERROR, interfaceName: interfaceName}, "", 0),
|
||||
}
|
||||
|
||||
logger.Debug.Println("Debug log enabled")
|
||||
|
||||
tun, name, err := tun.CreateTUNFromFD(int(tun_fd))
|
||||
if err != nil {
|
||||
unix.Close(int(tun_fd))
|
||||
logger.Error.Println(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
logger.Info.Println("Attaching to interface", name)
|
||||
device := NewDevice(tun, logger)
|
||||
|
||||
logger.Debug.Println("Interface has MTU", device.tun.mtu)
|
||||
|
||||
bufferedSettings := bufio.NewReadWriter(bufio.NewReader(strings.NewReader(settings)), bufio.NewWriter(ioutil.Discard))
|
||||
setError := ipcSetOperation(device, bufferedSettings)
|
||||
if setError != nil {
|
||||
unix.Close(int(tun_fd))
|
||||
logger.Error.Println(setError)
|
||||
return -1
|
||||
}
|
||||
|
||||
device.Up()
|
||||
logger.Info.Println("Device started")
|
||||
|
||||
var i int32
|
||||
for i = 0; i < math.MaxInt32; i++ {
|
||||
if _, exists := tunnelHandles[i]; !exists {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == math.MaxInt32 {
|
||||
unix.Close(int(tun_fd))
|
||||
return -1
|
||||
}
|
||||
tunnelHandles[i] = device
|
||||
return i
|
||||
}
|
||||
|
||||
//export wgTurnOff
|
||||
func wgTurnOff(tunnelHandle int32) {
|
||||
device, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(tunnelHandles, tunnelHandle)
|
||||
device.Close()
|
||||
}
|
||||
|
||||
//export wgGetSocketV4
|
||||
func wgGetSocketV4(tunnelHandle int32) int32 {
|
||||
device, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
native, ok := device.net.bind.(*NativeBind)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
fd := int32(-1)
|
||||
conn, err := native.ipv4.SyscallConn()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
err = conn.Control(func(f uintptr) {
|
||||
fd = int32(f)
|
||||
})
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return fd
|
||||
}
|
||||
|
||||
//export wgGetSocketV6
|
||||
func wgGetSocketV6(tunnelHandle int32) int32 {
|
||||
device, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
native, ok := device.net.bind.(*NativeBind)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
fd := int32(-1)
|
||||
conn, err := native.ipv6.SyscallConn()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
err = conn.Control(func(f uintptr) {
|
||||
fd = int32(f)
|
||||
})
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return fd
|
||||
}
|
||||
|
||||
func main() {}
|
||||
@@ -1,31 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"git.zx2c4.com/wireguard-go/rwcancel"
|
||||
"os"
|
||||
)
|
||||
|
||||
func CreateTUNFromFD(tun_fd int) (TUNDevice, string, error) {
|
||||
tun := &nativeTun{
|
||||
fd: os.NewFile(uintptr(tun_fd), "/dev/tun"),
|
||||
events: make(chan TUNEvent, 5),
|
||||
errors: make(chan error, 5),
|
||||
nopi: true,
|
||||
}
|
||||
var err error
|
||||
tun.fdCancel, err = rwcancel.NewRWCancel(tun_fd)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
name, err := tun.Name()
|
||||
if err != nil {
|
||||
tun.fdCancel.Cancel()
|
||||
return nil, "", err
|
||||
}
|
||||
return tun, name, nil
|
||||
}
|
||||
Submodule app/tools/wireguard deleted from 6977826c2c
Submodule app/tools/wireguard-go deleted from 778ff80424
@@ -1,20 +0,0 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.2'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.kapt) apply false
|
||||
}
|
||||
|
||||
tasks {
|
||||
wrapper {
|
||||
gradleVersion = "8.1.1"
|
||||
distributionSha256Sum = "e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f"
|
||||
}
|
||||
}
|
||||
+66
-10
@@ -1,17 +1,73 @@
|
||||
# Project-wide Gradle settings.
|
||||
wireguardVersionCode=505
|
||||
wireguardVersionName=1.0.20230512
|
||||
wireguardPackageName=com.wireguard.android
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.caching=true
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Enable Kotlin incremental compilation
|
||||
kotlin.incremental=true
|
||||
|
||||
# Enable AndroidX support
|
||||
android.useAndroidX=true
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# Turn off AP discovery in compile path to enable compile avoidance
|
||||
kapt.include.compile.classpath=false
|
||||
|
||||
# Enable non-transitive R class namespacing where each library only contains
|
||||
# references to the resources it declares instead of declarations plus all
|
||||
# transitive dependency references.
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
# Experimental AGP flags
|
||||
# Generate compile-time only R class for app modules.
|
||||
android.enableAppCompileTimeRClass=true
|
||||
# Keep AAPT2 daemons alive between incremental builds.
|
||||
android.keepWorkerActionServicesBetweenBuilds=true
|
||||
# Make R fields non-final to improve build speeds.
|
||||
# http://tools.android.com/tips/non-constant-fields
|
||||
android.nonFinalResIds=true
|
||||
# Enable the newly refactored resource shrinker.
|
||||
android.experimental.enableNewResourceShrinker=true
|
||||
# Enable precise shrinking in the new resource shrinker.
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
||||
# Generate manifest class as a .class directly rather than a Java source file.
|
||||
android.generateManifestClass=true
|
||||
# Generate the text map of source sets and absolute paths to allow
|
||||
# generating relative paths from absolute paths later in the build.
|
||||
android.experimental.enableSourceSetPathsMap=true
|
||||
# Use relative paths for better Gradle caching of library build tasks
|
||||
android.experimental.cacheCompileLibResources=true
|
||||
|
||||
# Default Android build features
|
||||
# Disable BuildConfig generation by default
|
||||
android.defaults.buildfeatures.buildconfig=false
|
||||
# Disable AIDL stub generation by default
|
||||
android.defaults.buildfeatures.aidl=false
|
||||
# Disable RenderScript compilation by default
|
||||
android.defaults.buildfeatures.renderscript=false
|
||||
# Disable resource values generation by default in libraries
|
||||
android.defaults.buildfeatures.resvalues=false
|
||||
# Disable shader compilation by default
|
||||
android.defaults.buildfeatures.shaders=false
|
||||
# Disable Android resource processing by default
|
||||
android.library.defaults.buildfeatures.androidresources=false
|
||||
|
||||
# Suppress warnings for some features that aren't yet stabilized
|
||||
android.suppressUnsupportedOptionWarnings=android.keepWorkerActionServicesBetweenBuilds,\
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking,\
|
||||
android.enableAppCompileTimeRClass,\
|
||||
android.suppressUnsupportedOptionWarnings
|
||||
|
||||
# OSSRH sometimes struggles with slow deployments, so this makes Gradle
|
||||
# more tolerant to those delays.
|
||||
systemProp.org.gradle.internal.http.connectionTimeout=500000
|
||||
systemProp.org.gradle.internal.http.socketTimeout=500000
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[versions]
|
||||
agp = "8.1.0-beta02"
|
||||
kotlin = "1.8.21"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-ktx = "androidx.activity:activity-ktx:1.7.1"
|
||||
androidx-annotation = "androidx.annotation:annotation:1.6.0"
|
||||
androidx-appcompat = "androidx.appcompat:appcompat:1.6.1"
|
||||
androidx-biometric = "androidx.biometric:biometric:1.1.0"
|
||||
androidx-collection = "androidx.collection:collection:1.2.0"
|
||||
androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
androidx-coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
androidx-core-ktx = "androidx.core:core-ktx:1.10.0"
|
||||
androidx-datastore-preferences = "androidx.datastore:datastore-preferences:1.0.0"
|
||||
androidx-fragment-ktx = "androidx.fragment:fragment-ktx:1.5.7"
|
||||
androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
|
||||
androidx-preference-ktx = "androidx.preference:preference-ktx:1.2.0"
|
||||
desugarJdkLibs = "com.android.tools:desugar_jdk_libs:2.0.3"
|
||||
google-material = "com.google.android.material:material:1.9.0"
|
||||
jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
|
||||
junit = "junit:junit:4.13.2"
|
||||
kotlinx-coroutines-android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0"
|
||||
zxing-android-embedded = "com.journeyapps:zxing-android-embedded:4.3.0"
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
||||
Vendored
BIN
Binary file not shown.
+3
-1
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip
|
||||
|
||||
@@ -1,78 +1,126 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -81,7 +129,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -89,84 +137,109 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
include ':app'
|
||||
@@ -0,0 +1,22 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "wireguard-android"
|
||||
|
||||
include(":tunnel")
|
||||
include(":ui")
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
curl -Lo - https://crowdin.com/backend/download/project/wireguard.zip | bsdtar -C ui/src/main/res -x -f - --strip-components 5 wireguard-android
|
||||
find ui/src/main/res -name strings.xml -exec bash -c '[[ $(xmllint --xpath "count(//resources/*)" {}) -ne 0 ]] || rm -rf "$(dirname {})"' \;
|
||||
@@ -0,0 +1,128 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||
|
||||
val pkg: String = providers.gradleProperty("wireguardPackageName").get()
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 33
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
namespace = "${pkg}.tunnel"
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("tools/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
testOptions.unitTests.all {
|
||||
it.testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
|
||||
}
|
||||
buildTypes {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
targets("libwg-go.so", "libwg.so", "libwg-quick.so")
|
||||
arguments("-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
release {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_PACKAGE_NAME=${pkg}")
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_PACKAGE_NAME=${pkg}.debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lint {
|
||||
disable += "LongLogTag"
|
||||
disable += "NewApi"
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release") {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.collection)
|
||||
compileOnly(libs.jsr305)
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
register<MavenPublication>("release") {
|
||||
groupId = pkg
|
||||
artifactId = "tunnel"
|
||||
version = providers.gradleProperty("wireguardVersionName").get()
|
||||
afterEvaluate {
|
||||
from(components["release"])
|
||||
}
|
||||
pom {
|
||||
name.set("WireGuard Tunnel Library")
|
||||
description.set("Embeddable tunnel library for WireGuard for Android")
|
||||
url.set("https://www.wireguard.com/")
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name.set("The Apache Software License, Version 2.0")
|
||||
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
|
||||
distribution.set("repo")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection.set("scm:git:https://git.zx2c4.com/wireguard-android")
|
||||
developerConnection.set("scm:git:https://git.zx2c4.com/wireguard-android")
|
||||
url.set("https://git.zx2c4.com/wireguard-android")
|
||||
}
|
||||
developers {
|
||||
organization {
|
||||
name.set("WireGuard")
|
||||
url.set("https://www.wireguard.com/")
|
||||
}
|
||||
developer {
|
||||
name.set("WireGuard")
|
||||
email.set("team@wireguard.com")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
credentials {
|
||||
username = providers.environmentVariable("SONATYPE_USER").orNull
|
||||
password = providers.environmentVariable("SONATYPE_PASSWORD").orNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(publishing.publications)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
~ Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name="com.wireguard.android.backend.GoBackend$VpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for implementations of the WireGuard secure network tunnel.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
public interface Backend {
|
||||
/**
|
||||
* Enumerate names of currently-running tunnels.
|
||||
*
|
||||
* @return The set of running tunnel names.
|
||||
*/
|
||||
Set<String> getRunningTunnelNames();
|
||||
|
||||
/**
|
||||
* Get the state of a tunnel.
|
||||
*
|
||||
* @param tunnel The tunnel to examine the state of.
|
||||
* @return The state of the tunnel.
|
||||
* @throws Exception Exception raised when retrieving tunnel's state.
|
||||
*/
|
||||
Tunnel.State getState(Tunnel tunnel) throws Exception;
|
||||
|
||||
/**
|
||||
* Get statistics about traffic and errors on this tunnel. If the tunnel is not running, the
|
||||
* statistics object will be filled with zero values.
|
||||
*
|
||||
* @param tunnel The tunnel to retrieve statistics for.
|
||||
* @return The statistics for the tunnel.
|
||||
* @throws Exception Exception raised when retrieving statistics.
|
||||
*/
|
||||
Statistics getStatistics(Tunnel tunnel) throws Exception;
|
||||
|
||||
/**
|
||||
* Determine version of underlying backend.
|
||||
*
|
||||
* @return The version of the backend.
|
||||
* @throws Exception Exception raised while retrieving version.
|
||||
*/
|
||||
String getVersion() throws Exception;
|
||||
|
||||
/**
|
||||
* Set the state of a tunnel, updating it's configuration. If the tunnel is already up, config
|
||||
* may update the running configuration; config may be null when setting the tunnel down.
|
||||
*
|
||||
* @param tunnel The tunnel to control the state of.
|
||||
* @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
|
||||
* {@code TOGGLE}.
|
||||
* @param config The configuration for this tunnel, may be null if state is {@code DOWN}.
|
||||
* @return The updated state of the tunnel.
|
||||
* @throws Exception Exception raised while changing state.
|
||||
*/
|
||||
Tunnel.State setState(Tunnel tunnel, Tunnel.State state, @Nullable Config config) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
/**
|
||||
* A subclass of {@link Exception} that encapsulates the reasons for a failure originating in
|
||||
* implementations of {@link Backend}.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class BackendException extends Exception {
|
||||
private final Object[] format;
|
||||
private final Reason reason;
|
||||
|
||||
/**
|
||||
* Public constructor for BackendException.
|
||||
*
|
||||
* @param reason The {@link Reason} which caused this exception to be thrown
|
||||
* @param format Format string values used when converting exceptions to user-facing strings.
|
||||
*/
|
||||
public BackendException(final Reason reason, final Object... format) {
|
||||
this.reason = reason;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format string values associated with the instance.
|
||||
*
|
||||
* @return Array of {@link Object} for string formatting purposes
|
||||
*/
|
||||
public Object[] getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reason for this exception.
|
||||
*
|
||||
* @return Associated {@link Reason} for this exception.
|
||||
*/
|
||||
public Reason getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum class containing all known reasons for why a {@link BackendException} might be thrown.
|
||||
*/
|
||||
public enum Reason {
|
||||
UNKNOWN_KERNEL_MODULE_NAME,
|
||||
WG_QUICK_CONFIG_ERROR_CODE,
|
||||
TUNNEL_MISSING_CONFIG,
|
||||
VPN_NOT_AUTHORIZED,
|
||||
UNABLE_TO_START_VPN,
|
||||
TUN_CREATION_ERROR,
|
||||
GO_ACTIVATION_ERROR_CODE,
|
||||
DNS_RESOLUTION_FAILURE,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.backend.BackendException.Reason;
|
||||
import com.wireguard.android.backend.Tunnel.State;
|
||||
import com.wireguard.android.util.SharedLibraryLoader;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.config.InetEndpoint;
|
||||
import com.wireguard.config.InetNetwork;
|
||||
import com.wireguard.config.Peer;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Backend} that uses the wireguard-go userspace implementation to provide
|
||||
* WireGuard tunnels.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class GoBackend implements Backend {
|
||||
private static final int DNS_RESOLUTION_RETRIES = 10;
|
||||
private static final String TAG = "WireGuard/GoBackend";
|
||||
@Nullable private static AlwaysOnCallback alwaysOnCallback;
|
||||
private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>();
|
||||
private final Context context;
|
||||
@Nullable private Config currentConfig;
|
||||
@Nullable private Tunnel currentTunnel;
|
||||
private int currentTunnelHandle = -1;
|
||||
|
||||
/**
|
||||
* Public constructor for GoBackend.
|
||||
*
|
||||
* @param context An Android {@link Context}
|
||||
*/
|
||||
public GoBackend(final Context context) {
|
||||
SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link AlwaysOnCallback} to be invoked when {@link VpnService} is started by the
|
||||
* system's Always-On VPN mode.
|
||||
*
|
||||
* @param cb Callback to be invoked
|
||||
*/
|
||||
public static void setAlwaysOnCallback(final AlwaysOnCallback cb) {
|
||||
alwaysOnCallback = cb;
|
||||
}
|
||||
|
||||
@Nullable private static native String wgGetConfig(int handle);
|
||||
|
||||
private static native int wgGetSocketV4(int handle);
|
||||
|
||||
private static native int wgGetSocketV6(int handle);
|
||||
|
||||
private static native void wgTurnOff(int handle);
|
||||
|
||||
private static native int wgTurnOn(String ifName, int tunFd, String settings);
|
||||
|
||||
private static native String wgVersion();
|
||||
|
||||
/**
|
||||
* Method to get the names of running tunnels.
|
||||
*
|
||||
* @return A set of string values denoting names of running tunnels.
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getRunningTunnelNames() {
|
||||
if (currentTunnel != null) {
|
||||
final Set<String> runningTunnels = new ArraySet<>();
|
||||
runningTunnels.add(currentTunnel.getName());
|
||||
return runningTunnels;
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated {@link State} for a given {@link Tunnel}.
|
||||
*
|
||||
* @param tunnel The tunnel to examine the state of.
|
||||
* @return {@link State} associated with the given tunnel.
|
||||
*/
|
||||
@Override
|
||||
public State getState(final Tunnel tunnel) {
|
||||
return currentTunnel == tunnel ? State.UP : State.DOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated {@link Statistics} for a given {@link Tunnel}.
|
||||
*
|
||||
* @param tunnel The tunnel to retrieve statistics for.
|
||||
* @return {@link Statistics} associated with the given tunnel.
|
||||
*/
|
||||
@Override
|
||||
public Statistics getStatistics(final Tunnel tunnel) {
|
||||
final Statistics stats = new Statistics();
|
||||
if (tunnel != currentTunnel || currentTunnelHandle == -1)
|
||||
return stats;
|
||||
final String config = wgGetConfig(currentTunnelHandle);
|
||||
if (config == null)
|
||||
return stats;
|
||||
Key key = null;
|
||||
long rx = 0;
|
||||
long tx = 0;
|
||||
long latestHandshakeMSec = 0;
|
||||
for (final String line : config.split("\\n")) {
|
||||
if (line.startsWith("public_key=")) {
|
||||
if (key != null)
|
||||
stats.add(key, rx, tx, latestHandshakeMSec);
|
||||
rx = 0;
|
||||
tx = 0;
|
||||
latestHandshakeMSec = 0;
|
||||
try {
|
||||
key = Key.fromHex(line.substring(11));
|
||||
} catch (final KeyFormatException ignored) {
|
||||
key = null;
|
||||
}
|
||||
} else if (line.startsWith("rx_bytes=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
rx = Long.parseLong(line.substring(9));
|
||||
} catch (final NumberFormatException ignored) {
|
||||
rx = 0;
|
||||
}
|
||||
} else if (line.startsWith("tx_bytes=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
tx = Long.parseLong(line.substring(9));
|
||||
} catch (final NumberFormatException ignored) {
|
||||
tx = 0;
|
||||
}
|
||||
} else if (line.startsWith("last_handshake_time_sec=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
latestHandshakeMSec += Long.parseLong(line.substring(24)) * 1000;
|
||||
} catch (final NumberFormatException ignored) {
|
||||
latestHandshakeMSec = 0;
|
||||
}
|
||||
} else if (line.startsWith("last_handshake_time_nsec=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
latestHandshakeMSec += Long.parseLong(line.substring(25)) / 1000000;
|
||||
} catch (final NumberFormatException ignored) {
|
||||
latestHandshakeMSec = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key != null)
|
||||
stats.add(key, rx, tx, latestHandshakeMSec);
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of the underlying wireguard-go library.
|
||||
*
|
||||
* @return {@link String} value of the version of the wireguard-go library.
|
||||
*/
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return wgVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the state of a given {@link Tunnel}, optionally applying a given {@link Config}.
|
||||
*
|
||||
* @param tunnel The tunnel to control the state of.
|
||||
* @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
|
||||
* {@code TOGGLE}.
|
||||
* @param config The configuration for this tunnel, may be null if state is {@code DOWN}.
|
||||
* @return {@link State} of the tunnel after state changes are applied.
|
||||
* @throws Exception Exception raised while changing tunnel state.
|
||||
*/
|
||||
@Override
|
||||
public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception {
|
||||
final State originalState = getState(tunnel);
|
||||
|
||||
if (state == State.TOGGLE)
|
||||
state = originalState == State.UP ? State.DOWN : State.UP;
|
||||
if (state == originalState && tunnel == currentTunnel && config == currentConfig)
|
||||
return originalState;
|
||||
if (state == State.UP) {
|
||||
final Config originalConfig = currentConfig;
|
||||
final Tunnel originalTunnel = currentTunnel;
|
||||
if (currentTunnel != null)
|
||||
setStateInternal(currentTunnel, null, State.DOWN);
|
||||
try {
|
||||
setStateInternal(tunnel, config, state);
|
||||
} catch (final Exception e) {
|
||||
if (originalTunnel != null)
|
||||
setStateInternal(originalTunnel, originalConfig, State.UP);
|
||||
throw e;
|
||||
}
|
||||
} else if (state == State.DOWN && tunnel == currentTunnel) {
|
||||
setStateInternal(tunnel, null, State.DOWN);
|
||||
}
|
||||
return getState(tunnel);
|
||||
}
|
||||
|
||||
private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state)
|
||||
throws Exception {
|
||||
Log.i(TAG, "Bringing tunnel " + tunnel.getName() + ' ' + state);
|
||||
|
||||
if (state == State.UP) {
|
||||
if (config == null)
|
||||
throw new BackendException(Reason.TUNNEL_MISSING_CONFIG);
|
||||
|
||||
if (VpnService.prepare(context) != null)
|
||||
throw new BackendException(Reason.VPN_NOT_AUTHORIZED);
|
||||
|
||||
final VpnService service;
|
||||
if (!vpnService.isDone()) {
|
||||
Log.d(TAG, "Requesting to start VpnService");
|
||||
context.startService(new Intent(context, VpnService.class));
|
||||
}
|
||||
|
||||
try {
|
||||
service = vpnService.get(2, TimeUnit.SECONDS);
|
||||
} catch (final TimeoutException e) {
|
||||
final Exception be = new BackendException(Reason.UNABLE_TO_START_VPN);
|
||||
be.initCause(e);
|
||||
throw be;
|
||||
}
|
||||
service.setOwner(this);
|
||||
|
||||
if (currentTunnelHandle != -1) {
|
||||
Log.w(TAG, "Tunnel already up");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
dnsRetry: for (int i = 0; i < DNS_RESOLUTION_RETRIES; ++i) {
|
||||
// Pre-resolve IPs so they're cached when building the userspace string
|
||||
for (final Peer peer : config.getPeers()) {
|
||||
final InetEndpoint ep = peer.getEndpoint().orElse(null);
|
||||
if (ep == null)
|
||||
continue;
|
||||
if (ep.getResolved().orElse(null) == null) {
|
||||
if (i < DNS_RESOLUTION_RETRIES - 1) {
|
||||
Log.w(TAG, "DNS host \"" + ep.getHost() + "\" failed to resolve; trying again");
|
||||
Thread.sleep(1000);
|
||||
continue dnsRetry;
|
||||
} else
|
||||
throw new BackendException(Reason.DNS_RESOLUTION_FAILURE, ep.getHost());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Build config
|
||||
final String goConfig = config.toWgUserspaceString();
|
||||
|
||||
// Create the vpn tunnel with android API
|
||||
final VpnService.Builder builder = service.getBuilder();
|
||||
builder.setSession(tunnel.getName());
|
||||
|
||||
for (final String excludedApplication : config.getInterface().getExcludedApplications())
|
||||
builder.addDisallowedApplication(excludedApplication);
|
||||
|
||||
for (final String includedApplication : config.getInterface().getIncludedApplications())
|
||||
builder.addAllowedApplication(includedApplication);
|
||||
|
||||
for (final InetNetwork addr : config.getInterface().getAddresses())
|
||||
builder.addAddress(addr.getAddress(), addr.getMask());
|
||||
|
||||
for (final InetAddress addr : config.getInterface().getDnsServers())
|
||||
builder.addDnsServer(addr.getHostAddress());
|
||||
|
||||
for (final String dnsSearchDomain : config.getInterface().getDnsSearchDomains())
|
||||
builder.addSearchDomain(dnsSearchDomain);
|
||||
|
||||
boolean sawDefaultRoute = false;
|
||||
for (final Peer peer : config.getPeers()) {
|
||||
for (final InetNetwork addr : peer.getAllowedIps()) {
|
||||
if (addr.getMask() == 0)
|
||||
sawDefaultRoute = true;
|
||||
builder.addRoute(addr.getAddress(), addr.getMask());
|
||||
}
|
||||
}
|
||||
|
||||
// "Kill-switch" semantics
|
||||
if (!(sawDefaultRoute && config.getPeers().size() == 1)) {
|
||||
builder.allowFamily(OsConstants.AF_INET);
|
||||
builder.allowFamily(OsConstants.AF_INET6);
|
||||
}
|
||||
|
||||
builder.setMtu(config.getInterface().getMtu().orElse(1280));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
builder.setMetered(false);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
service.setUnderlyingNetworks(null);
|
||||
|
||||
builder.setBlocking(true);
|
||||
try (final ParcelFileDescriptor tun = builder.establish()) {
|
||||
if (tun == null)
|
||||
throw new BackendException(Reason.TUN_CREATION_ERROR);
|
||||
Log.d(TAG, "Go backend " + wgVersion());
|
||||
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
|
||||
}
|
||||
if (currentTunnelHandle < 0)
|
||||
throw new BackendException(Reason.GO_ACTIVATION_ERROR_CODE, currentTunnelHandle);
|
||||
|
||||
currentTunnel = tunnel;
|
||||
currentConfig = config;
|
||||
|
||||
service.protect(wgGetSocketV4(currentTunnelHandle));
|
||||
service.protect(wgGetSocketV6(currentTunnelHandle));
|
||||
} else {
|
||||
if (currentTunnelHandle == -1) {
|
||||
Log.w(TAG, "Tunnel already down");
|
||||
return;
|
||||
}
|
||||
int handleToClose = currentTunnelHandle;
|
||||
currentTunnel = null;
|
||||
currentTunnelHandle = -1;
|
||||
currentConfig = null;
|
||||
wgTurnOff(handleToClose);
|
||||
try {
|
||||
vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf();
|
||||
} catch (final TimeoutException ignored) { }
|
||||
}
|
||||
|
||||
tunnel.onStateChange(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for {@link GoBackend} that is invoked when {@link VpnService} is started by the
|
||||
* system's Always-On VPN mode.
|
||||
*/
|
||||
public interface AlwaysOnCallback {
|
||||
void alwaysOnTriggered();
|
||||
}
|
||||
|
||||
// TODO: When we finally drop API 21 and move to API 24, delete this and replace with the ordinary CompletableFuture.
|
||||
private static final class GhettoCompletableFuture<V> {
|
||||
private final LinkedBlockingQueue<V> completion = new LinkedBlockingQueue<>(1);
|
||||
private final FutureTask<V> result = new FutureTask<>(completion::peek);
|
||||
|
||||
public boolean complete(final V value) {
|
||||
final boolean offered = completion.offer(value);
|
||||
if (offered)
|
||||
result.run();
|
||||
return offered;
|
||||
}
|
||||
|
||||
public V get() throws ExecutionException, InterruptedException {
|
||||
return result.get();
|
||||
}
|
||||
|
||||
public V get(final long timeout, final TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
|
||||
return result.get(timeout, unit);
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return !completion.isEmpty();
|
||||
}
|
||||
|
||||
public GhettoCompletableFuture<V> newIncompleteFuture() {
|
||||
return new GhettoCompletableFuture<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link android.net.VpnService} implementation for {@link GoBackend}
|
||||
*/
|
||||
public static class VpnService extends android.net.VpnService {
|
||||
@Nullable private GoBackend owner;
|
||||
|
||||
public Builder getBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
vpnService.complete(this);
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (owner != null) {
|
||||
final Tunnel tunnel = owner.currentTunnel;
|
||||
if (tunnel != null) {
|
||||
if (owner.currentTunnelHandle != -1)
|
||||
wgTurnOff(owner.currentTunnelHandle);
|
||||
owner.currentTunnel = null;
|
||||
owner.currentTunnelHandle = -1;
|
||||
owner.currentConfig = null;
|
||||
tunnel.onStateChange(State.DOWN);
|
||||
}
|
||||
}
|
||||
vpnService = vpnService.newIncompleteFuture();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
|
||||
vpnService.complete(this);
|
||||
if (intent == null || intent.getComponent() == null || !intent.getComponent().getPackageName().equals(getPackageName())) {
|
||||
Log.d(TAG, "Service started by Always-on VPN feature");
|
||||
if (alwaysOnCallback != null)
|
||||
alwaysOnCallback.alwaysOnTriggered();
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
public void setOwner(final GoBackend owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Class representing transfer statistics for a {@link Tunnel} instance.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public class Statistics {
|
||||
|
||||
// TODO: switch to Java Record class once R8 supports desugaring those.
|
||||
public final class PeerStats {
|
||||
public final long rxBytes, txBytes, latestHandshakeEpochMillis;
|
||||
|
||||
PeerStats(final long rxBytes, final long txBytes, final long latestHandshakeEpochMillis) {
|
||||
this.rxBytes = rxBytes;
|
||||
this.txBytes = txBytes;
|
||||
this.latestHandshakeEpochMillis = latestHandshakeEpochMillis;
|
||||
}
|
||||
|
||||
@Override public boolean equals(final Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
final PeerStats stats = (PeerStats) o;
|
||||
return rxBytes == stats.rxBytes && txBytes == stats.txBytes && latestHandshakeEpochMillis == stats.latestHandshakeEpochMillis;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Objects.hash(rxBytes, txBytes, latestHandshakeEpochMillis);
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Key, PeerStats> stats = new HashMap<>();
|
||||
private long lastTouched = SystemClock.elapsedRealtime();
|
||||
|
||||
Statistics() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a peer and its current stats to the internal map.
|
||||
*
|
||||
* @param key A WireGuard public key bound to a particular peer
|
||||
* @param rxBytes The received traffic for the {@link com.wireguard.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes
|
||||
* @param txBytes The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes.
|
||||
* @param latestHandshake The timestamp of the latest handshake for the {@link com.wireguard.config.Peer}
|
||||
* referenced by the provided {@link Key}. The value is in epoch milliseconds.
|
||||
*/
|
||||
void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) {
|
||||
stats.put(key, new PeerStats(rxBytes, txBytes, latestHandshake));
|
||||
lastTouched = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the statistics are stale, indicating the need for the {@link Backend} to update them.
|
||||
*
|
||||
* @return boolean indicating if the current statistics instance has stale values.
|
||||
*/
|
||||
public boolean isStale() {
|
||||
return SystemClock.elapsedRealtime() - lastTouched > 900;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the statistics for the {@link com.wireguard.config.Peer} referenced by the provided {@link Key}
|
||||
*
|
||||
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
|
||||
* @return a {@link PeerStats} representing various statistics about this peer.
|
||||
*/
|
||||
@Nullable
|
||||
public PeerStats peer(final Key peer) {
|
||||
return this.stats.get(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of peers being tracked by this instance.
|
||||
*
|
||||
* @return An array of {@link Key} instances representing WireGuard
|
||||
* {@link com.wireguard.config.Peer}s
|
||||
*/
|
||||
public Key[] peers() {
|
||||
return stats.keySet().toArray(new Key[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total received traffic by all the peers being tracked by this instance
|
||||
*
|
||||
* @return a long representing the number of bytes received by the peers being tracked.
|
||||
*/
|
||||
public long totalRx() {
|
||||
long rx = 0;
|
||||
for (final PeerStats val : stats.values()) {
|
||||
rx += val.rxBytes;
|
||||
}
|
||||
return rx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total transmitted traffic by all the peers being tracked by this instance
|
||||
*
|
||||
* @return a long representing the number of bytes transmitted by the peers being tracked.
|
||||
*/
|
||||
public long totalTx() {
|
||||
long tx = 0;
|
||||
for (final PeerStats val : stats.values()) {
|
||||
tx += val.txBytes;
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents a WireGuard tunnel.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
public interface Tunnel {
|
||||
int NAME_MAX_LENGTH = 15;
|
||||
Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}");
|
||||
|
||||
static boolean isNameInvalid(final CharSequence name) {
|
||||
return !NAME_PATTERN.matcher(name).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the tunnel, which should always pass the !isNameInvalid test.
|
||||
*
|
||||
* @return The name of the tunnel.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* React to a change in state of the tunnel. Should only be directly called by Backend.
|
||||
*
|
||||
* @param newState The new state of the tunnel.
|
||||
*/
|
||||
void onStateChange(State newState);
|
||||
|
||||
/**
|
||||
* Enum class to represent all possible states of a {@link Tunnel}.
|
||||
*/
|
||||
enum State {
|
||||
DOWN,
|
||||
TOGGLE,
|
||||
UP;
|
||||
|
||||
/**
|
||||
* Get the state of a {@link Tunnel}
|
||||
*
|
||||
* @param running boolean indicating if the tunnel is running.
|
||||
* @return State of the tunnel based on whether or not it is running.
|
||||
*/
|
||||
public static State of(final boolean running) {
|
||||
return running ? UP : DOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.wireguard.android.backend.BackendException.Reason;
|
||||
import com.wireguard.android.backend.Tunnel.State;
|
||||
import com.wireguard.android.util.RootShell;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Backend} that uses the kernel module and {@code wg-quick} to provide
|
||||
* WireGuard tunnels.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
public final class WgQuickBackend implements Backend {
|
||||
private static final String TAG = "WireGuard/WgQuickBackend";
|
||||
private final File localTemporaryDir;
|
||||
private final RootShell rootShell;
|
||||
private final Map<Tunnel, Config> runningConfigs = new HashMap<>();
|
||||
private final ToolsInstaller toolsInstaller;
|
||||
private boolean multipleTunnels;
|
||||
|
||||
public WgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) {
|
||||
localTemporaryDir = new File(context.getCacheDir(), "tmp");
|
||||
this.rootShell = rootShell;
|
||||
this.toolsInstaller = toolsInstaller;
|
||||
}
|
||||
|
||||
public static boolean hasKernelSupport() {
|
||||
return new File("/sys/module/wireguard").exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRunningTunnelNames() {
|
||||
final List<String> output = new ArrayList<>();
|
||||
// Don't throw an exception here or nothing will show up in the UI.
|
||||
try {
|
||||
toolsInstaller.ensureToolsAvailable();
|
||||
if (rootShell.run(output, "wg show interfaces") != 0 || output.isEmpty())
|
||||
return Collections.emptySet();
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, "Unable to enumerate running tunnels", e);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
// wg puts all interface names on the same line. Split them into separate elements.
|
||||
return Set.of(output.get(0).split(" "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState(final Tunnel tunnel) {
|
||||
return getRunningTunnelNames().contains(tunnel.getName()) ? State.UP : State.DOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statistics getStatistics(final Tunnel tunnel) {
|
||||
final Statistics stats = new Statistics();
|
||||
final Collection<String> output = new ArrayList<>();
|
||||
try {
|
||||
if (rootShell.run(output, String.format("wg show '%s' dump", tunnel.getName())) != 0)
|
||||
return stats;
|
||||
} catch (final Exception ignored) {
|
||||
return stats;
|
||||
}
|
||||
for (final String line : output) {
|
||||
final String[] parts = line.split("\\t");
|
||||
if (parts.length != 8)
|
||||
continue;
|
||||
try {
|
||||
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[5]), Long.parseLong(parts[6]), Long.parseLong(parts[4]) * 1000);
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() throws Exception {
|
||||
final List<String> output = new ArrayList<>();
|
||||
if (rootShell.run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty())
|
||||
throw new BackendException(Reason.UNKNOWN_KERNEL_MODULE_NAME);
|
||||
return output.get(0);
|
||||
}
|
||||
|
||||
public void setMultipleTunnels(final boolean on) {
|
||||
multipleTunnels = on;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception {
|
||||
final State originalState = getState(tunnel);
|
||||
final Config originalConfig = runningConfigs.get(tunnel);
|
||||
final Map<Tunnel, Config> runningConfigsSnapshot = new HashMap<>(runningConfigs);
|
||||
|
||||
if (state == State.TOGGLE)
|
||||
state = originalState == State.UP ? State.DOWN : State.UP;
|
||||
if ((state == State.UP && originalState == State.UP && originalConfig != null && originalConfig == config) ||
|
||||
(state == State.DOWN && originalState == State.DOWN))
|
||||
return originalState;
|
||||
if (state == State.UP) {
|
||||
toolsInstaller.ensureToolsAvailable();
|
||||
if (!multipleTunnels && originalState == State.DOWN) {
|
||||
final List<Pair<Tunnel, Config>> rewind = new LinkedList<>();
|
||||
try {
|
||||
for (final Map.Entry<Tunnel, Config> entry : runningConfigsSnapshot.entrySet()) {
|
||||
setStateInternal(entry.getKey(), entry.getValue(), State.DOWN);
|
||||
rewind.add(Pair.create(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
try {
|
||||
for (final Pair<Tunnel, Config> entry : rewind) {
|
||||
setStateInternal(entry.first, entry.second, State.UP);
|
||||
}
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (originalState == State.UP)
|
||||
setStateInternal(tunnel, originalConfig == null ? config : originalConfig, State.DOWN);
|
||||
try {
|
||||
setStateInternal(tunnel, config, State.UP);
|
||||
} catch (final Exception e) {
|
||||
try {
|
||||
if (originalState == State.UP && originalConfig != null) {
|
||||
setStateInternal(tunnel, originalConfig, State.UP);
|
||||
}
|
||||
if (!multipleTunnels && originalState == State.DOWN) {
|
||||
for (final Map.Entry<Tunnel, Config> entry : runningConfigsSnapshot.entrySet()) {
|
||||
setStateInternal(entry.getKey(), entry.getValue(), State.UP);
|
||||
}
|
||||
}
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
} else if (state == State.DOWN) {
|
||||
setStateInternal(tunnel, originalConfig == null ? config : originalConfig, State.DOWN);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) throws Exception {
|
||||
Log.i(TAG, "Bringing tunnel " + tunnel.getName() + ' ' + state);
|
||||
|
||||
Objects.requireNonNull(config, "Trying to set state up with a null config");
|
||||
|
||||
final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf");
|
||||
try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) {
|
||||
stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
String command = String.format("wg-quick %s '%s'",
|
||||
state.toString().toLowerCase(Locale.ENGLISH), tempFile.getAbsolutePath());
|
||||
if (state == State.UP)
|
||||
command = "cat /sys/module/wireguard/version && " + command;
|
||||
final int result = rootShell.run(null, command);
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
tempFile.delete();
|
||||
if (result != 0)
|
||||
throw new BackendException(Reason.WG_QUICK_CONFIG_ERROR_CODE, result);
|
||||
|
||||
if (state == State.UP)
|
||||
runningConfigs.put(tunnel, config);
|
||||
else
|
||||
runningConfigs.remove(tunnel);
|
||||
|
||||
tunnel.onStateChange(state);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user